From 7ecb57d6bc4e6da1e9cdb1cf102e3a5430670be3 Mon Sep 17 00:00:00 2001 From: Piotr Fusik Date: Mon, 20 Dec 2021 11:52:47 +0100 Subject: [PATCH] New version of the QOI format. --- Makefile | 8 +- QOI.ci | 230 +++++++++++++++++------------------ QOIPaintDotNet.cs | 6 +- Xqoi.c | 6 +- file-qoi.c | 4 +- png2qoi.c | 2 +- transpiled/QOI.c | 186 +++++++++++++++-------------- transpiled/QOI.cpp | 186 +++++++++++++++-------------- transpiled/QOI.cs | 207 ++++++++++++++++---------------- transpiled/QOI.h | 25 +--- transpiled/QOI.hpp | 38 ++---- transpiled/QOI.js | 213 ++++++++++++++++----------------- transpiled/QOI.py | 199 +++++++++++++------------------ transpiled/QOI.swift | 238 ++++++++++++++++--------------------- transpiled/QOIDecoder.java | 98 ++++++++------- transpiled/QOIEncoder.java | 94 +++++++-------- win32/win32.mk | 2 +- 17 files changed, 829 insertions(+), 913 deletions(-) diff --git a/Makefile b/Makefile index 3cec8f6..9ec5e1f 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,12 @@ XNVIEW_DIR = /opt/XnView endif CSC = "C:/Program Files/Microsoft Visual Studio/2022/Community/MSBuild/Current/Bin/Roslyn/csc.exe" -nologo PAINT_NET_DIR = C:/Program Files/paint.net -DOTNET_REF_DIR = C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.0/ref/net6.0 +DOTNET_REF_DIR = C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.1/ref/net6.0 TRANSPILED = $(addprefix transpiled/QOI., c cpp cs js py swift) transpiled/QOIDecoder.java -all: png2qoi Xqoi.usr $(TRANSPILED) +all: png2qoi$(EXEEXT) Xqoi.usr $(TRANSPILED) -png2qoi: png2qoi.c QOI-stdio.c QOI-stdio.h transpiled/QOI.c +png2qoi$(EXEEXT): png2qoi.c QOI-stdio.c QOI-stdio.h transpiled/QOI.c $(CC) $(CFLAGS) -I transpiled -o $@ png2qoi.c QOI-stdio.c transpiled/QOI.c -lpng file-qoi$(EXEEXT): file-qoi.c QOI-stdio.c QOI-stdio.h transpiled/QOI.c @@ -45,7 +45,7 @@ $(TRANSPILED): QOI.ci mkdir -p $(@D) && cito -o $@ $^ clean: - $(RM) png2qoi file-qoi$(EXEEXT) Xqoi.usr QOIPaintDotNet.dll $(TRANSPILED) transpiled/QOI.h transpiled/QOI.hpp transpiled/QOIColorspace.java transpiled/QOIEncoder.java + $(RM) png2qoi$(EXEEXT) file-qoi$(EXEEXT) Xqoi.usr QOIPaintDotNet.dll $(TRANSPILED) transpiled/QOI.h transpiled/QOI.hpp transpiled/QOIColorspace.java transpiled/QOIEncoder.java .PHONY: all install-gimp install-xnview install-paint.net clean diff --git a/QOI.ci b/QOI.ci index 2b0610e..f2c12e8 100644 --- a/QOI.ci +++ b/QOI.ci @@ -22,24 +22,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -/// QOI color space metadata. -/// Saved in the file header, but doesn't affect encoding or decoding in any way. -public static class QOIColorspace -{ - /// sRGBA. - public const int Srgb = 0x00; - /// sRGB with linear alpha. - public const int SrgbLinearAlpha = 0x01; - /// Linear RGBA. - public const int Linear = 0x0f; -} - /// Encoder of the "Quite OK Image" (QOI) format. /// Losslessly compresses an image to a byte array. public class QOIEncoder { internal const int HeaderSize = 14; - internal const int PaddingSize = 4; + internal const int PaddingSize = 8; byte[]# Encoded; int EncodedSize; @@ -72,8 +60,11 @@ public class QOIEncoder int[] pixels, /// `false` specifies that all pixels are opaque. High bytes of `pixels` elements are ignored then. bool alpha, - /// Specifies the color space. See `QOIColorspace`. - int colorspace) + /// Specifies the color space. + /// `false` = sRGB with linear alpha channel. + /// `true` = all channels linear. + /// This is written in the header, but doesn't affect decoding in any way. + bool linearColorspace) { if (pixels == null || !CanEncode(width, height, alpha)) return false; @@ -92,7 +83,7 @@ public class QOIEncoder encoded[10] = height >> 8 & 0xff; encoded[11] = height & 0xff; encoded[12] = alpha ? 4 : 3; - encoded[13] = colorspace; + encoded[13] = linearColorspace ? 1 : 0; int[64] index = 0; int encodedOffset = HeaderSize; @@ -102,20 +93,18 @@ public class QOIEncoder int pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= 0xff << 24; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 0x2020 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = 0x40 + run - 1; - else { - run -= 33; - encoded[encodedOffset++] = 0x60 + (run >> 8); - encoded[encodedOffset++] = run & 0xff; + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = 0xc0 - 1 + run; + run = 0; } - run = 0; } - if (pixel != lastPixel) { - int indexOffset = ((pixel >> 24) ^ (pixel >> 16) ^ (pixel >> 8) ^ pixel) & 0x3f; + else { + if (run > 0) { + encoded[encodedOffset++] = 0xc0 - 1 + run; + run = 0; + } + int indexOffset = (pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 0x3f) * 7 + (pixel >> 24) * 11 & 0x3f; if (pixel == index[indexOffset]) encoded[encodedOffset++] = indexOffset; else { @@ -124,50 +113,48 @@ public class QOIEncoder int g = pixel >> 8 & 0xff; int b = pixel & 0xff; int a = pixel >> 24 & 0xff; - int dr = r - (lastPixel >> 16 & 0xff); - int dg = g - (lastPixel >> 8 & 0xff); - int db = b - (lastPixel & 0xff); - int da = a - (lastPixel >> 24 & 0xff); - if (dr >= -16 && dr <= 15 - && dg >= -16 && dg <= 15 - && db >= -16 && db <= 15 - && da >= -16 && da <= 15) { - if (da == 0 - && dr >= -2 && dr <= 1 + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = 0xff; + encoded[encodedOffset + 1] = r; + encoded[encodedOffset + 2] = g; + encoded[encodedOffset + 3] = b; + encoded[encodedOffset + 4] = a; + encodedOffset += 5; + } + else { + int dr = r - (lastPixel >> 16 & 0xff); + int dg = g - (lastPixel >> 8 & 0xff); + int db = b - (lastPixel & 0xff); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = 0x80 + (2 << 4) + (2 << 2) + 2 + (dr << 4) + (dg << 2) + db; - else if (da == 0 - && dg >= -8 && dg <= 7 - && db >= -8 && db <= 7) { - encoded[encodedOffset++] = 0xc0 + 16 + dr; - encoded[encodedOffset++] = (8 << 4) + 8 + (dg << 4) + db; - } + encoded[encodedOffset++] = 0x40 + (2 << 4) + (2 << 2) + 2 + (dr << 4) + (dg << 2) + db; else { - dr += 16; - encoded[encodedOffset++] = 0xe0 + (dr >> 1); - db += 16; - encoded[encodedOffset++] = ((dr & 1) << 7) + (dg + 16 << 2) + (db >> 3); - encoded[encodedOffset++] = ((db & 7) << 5) + da + 16; + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 + && dg >= -32 && dg <= 31 + && db >= -8 && db <= 7) { + encoded[encodedOffset] = 0x80 + 32 + dg; + encoded[encodedOffset + 1] = 0x88 + (dr << 4) + db; + encodedOffset += 2; + } + else { + encoded[encodedOffset] = 0xfe; + encoded[encodedOffset + 1] = r; + encoded[encodedOffset + 2] = g; + encoded[encodedOffset + 3] = b; + encodedOffset += 4; + } } } - else { - encoded[encodedOffset++] = 0xf0 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0); - if (dr != 0) - encoded[encodedOffset++] = r; - if (dg != 0) - encoded[encodedOffset++] = g; - if (db != 0) - encoded[encodedOffset++] = b; - if (da != 0) - encoded[encodedOffset++] = a; - } } lastPixel = pixel; } } - encoded.Fill(0, encodedOffset, PaddingSize); + encoded.Fill(0, encodedOffset, PaddingSize - 1); + encoded[encodedOffset + PaddingSize - 1] = 1; Encoded = encoded; EncodedSize = encodedOffset + PaddingSize; return true; @@ -190,7 +177,7 @@ public class QOIDecoder int Height; int[]# Pixels; bool Alpha; - int Colorspace; + bool LinearColorspace; /// Constructs the decoder. /// The decoder can be used for several images, one after another. @@ -214,6 +201,26 @@ public class QOIDecoder int height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11]; if (width <= 0 || height <= 0 || height > 0x7fffffff / width) return false; + switch (encoded[12]) { + case 3: + Alpha = false; + break; + case 4: + Alpha = true; + break; + default: + return false; + } + switch (encoded[13]) { + case 0: + LinearColorspace = false; + break; + case 1: + LinearColorspace = true; + break; + default: + return false; + } int pixelsSize = width * height; int[]# pixels = new int[pixelsSize]; @@ -225,55 +232,53 @@ public class QOIDecoder if (encodedOffset >= encodedSize) return false; int e = encoded[encodedOffset++]; - if (e < 0x80) { - if (e < 0x40) // 00iiiiii - pixels[pixelsOffset++] = pixel = index[e]; - else { - int run; - if (e < 0x60) // // 010rrrrr - run = e - (0x40 - 1); - else // 011rrrrr rrrrrrrr - run = 33 + (e - 0x60 << 8) + encoded[encodedOffset++]; - if (pixelsOffset + run > pixelsSize) - return false; - pixels.Fill(pixel, pixelsOffset, run); - pixelsOffset += run; - } + switch (e >> 6) { + case 0: // 00iiiiii + pixels[pixelsOffset++] = pixel = index[e]; continue; - } - else if (e < 0xe0) { - if (e < 0xc0) // 10rrggbb - pixel = (pixel & 0xff << 24) - | (pixel + ((e >> 4) - 8 - 2 << 16) & 0xff << 16) - | (pixel + ((e >> 2 & 3) - 2 << 8) & 0xff << 8) - | (pixel + ((e & 3) - 2) & 0xff); - else { // 110rrrrr ggggbbbb - int d = encoded[encodedOffset++]; + case 1: // 01rrggbb + pixel = (pixel & 0xff << 24) + | (pixel + ((e >> 4) - 4 - 2 << 16) & 0xff << 16) + | (pixel + ((e >> 2 & 3) - 2 << 8) & 0xff << 8) + | (pixel + ((e & 3) - 2) & 0xff); + break; + case 2: // 01gggggg rrrrbbbbb + e -= 0x80 + 32; + int rb = encoded[encodedOffset++]; + pixel = (pixel & 0xff << 24) + | (pixel + (e + (rb >> 4) - 8 << 16) & 0xff << 16) + | (pixel + (e << 8) & 0xff << 8) + | (pixel + e + (rb & 0xf) - 8 & 0xff); + break; + default: // 3 + if (e < 0xfe) { + // 11rrrrrr + e -= 0xc0 - 1; + if (pixelsOffset + e > pixelsSize) + return false; + pixels.Fill(pixel, pixelsOffset, e); + pixelsOffset += e; + continue; + } + if (e == 0xfe) { + // 11111110 rrrrrrrr gggggggg bbbbbbbb pixel = (pixel & 0xff << 24) - | (pixel + (e - 0xc0 - 16 << 16) & 0xff << 16) - | (pixel + ((d >> 4) - 8 << 8) & 0xff << 8) - | (pixel + ((d & 0xf) - 8) & 0xff); + | encoded[encodedOffset] << 16 + | encoded[encodedOffset + 1] << 8 + | encoded[encodedOffset + 2]; + encodedOffset += 3; } + else { + // 11111111 rrrrrrrr gggggggg bbbbbbbb aaaaaaaa + pixel = encoded[encodedOffset + 3] << 24 + | encoded[encodedOffset] << 16 + | encoded[encodedOffset + 1] << 8 + | encoded[encodedOffset + 2]; + encodedOffset += 4; + } + break; } - else if (e < 0xf0) { // 1110rrrr rgggggbb bbbaaaaa - e = e << 16 | encoded[encodedOffset] << 8 | encoded[encodedOffset + 1]; - encodedOffset += 2; - pixel = (pixel + ((e & 0x1f) - 16 << 24) & 0xff << 24) - | (pixel + ((e >> 15) - 0x1c0 - 16 << 16) & 0xff << 16) - | (pixel + ((e >> 10 & 0x1f) - 16 << 8) & 0xff << 8) - | (pixel + (e >> 5 & 0x1f) - 16 & 0xff); - } - else { // 1111rgba [rrrrrrrr] [gggggggg] [bbbbbbbb] [aaaaaaaa] - if ((e & 8) != 0) - pixel = (pixel & ~(0xff << 16)) | encoded[encodedOffset++] << 16; - if ((e & 4) != 0) - pixel = (pixel & ~(0xff << 8)) | encoded[encodedOffset++] << 8; - if ((e & 2) != 0) - pixel = (pixel & ~0xff) | encoded[encodedOffset++]; - if ((e & 1) != 0) - pixel = (pixel & ~(0xff << 24)) | encoded[encodedOffset++] << 24; - } - pixels[pixelsOffset++] = index[((pixel >> 24) ^ (pixel >> 16) ^ (pixel >> 8) ^ pixel) & 0x3f] = pixel; + pixels[pixelsOffset++] = index[(pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 0x3f) * 7 + (pixel >> 24) * 11 & 0x3f] = pixel; } if (encodedOffset != encodedSize) return false; @@ -281,8 +286,6 @@ public class QOIDecoder Width = width; Height = height; Pixels = pixels; - Alpha = encoded[12] == 4; - Colorspace = encoded[13]; return true; } @@ -297,9 +300,10 @@ public class QOIDecoder public int[] GetPixels() => Pixels; /// Returns the information about the alpha channel from the file header. - public bool GetAlpha() => Alpha; + public bool HasAlpha() => Alpha; /// Returns the color space information from the file header. - /// See `QOIColorspace`. - public int GetColorspace() => Colorspace; + /// `false` = sRGB with linear alpha channel. + /// `true` = all channels linear. + public bool IsLinearColorspace() => LinearColorspace; } diff --git a/QOIPaintDotNet.cs b/QOIPaintDotNet.cs index ac4ecdf..fdfdf2b 100644 --- a/QOIPaintDotNet.cs +++ b/QOIPaintDotNet.cs @@ -33,8 +33,8 @@ [assembly: AssemblyTitle("Paint.NET Quite OK Image (QOI) plugin")] [assembly: AssemblyCompany("Piotr Fusik")] [assembly: AssemblyCopyright("Copyright © 2021")] -[assembly: AssemblyVersion("0.0.3.0")] -[assembly: AssemblyFileVersion("0.0.3.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] namespace QOI.PaintDotNet { @@ -88,7 +88,7 @@ protected override void OnSave(Document input, Stream output, SaveConfigToken to // Encode QOIEncoder qoi = new QOIEncoder(); - if (!qoi.Encode(width, height, pixels, true, QOIColorspace.Srgb)) + if (!qoi.Encode(width, height, pixels, true, false)) throw new Exception("Error encoding QOI"); // Write diff --git a/Xqoi.c b/Xqoi.c index 15a74b8..e1e34bb 100644 --- a/Xqoi.c +++ b/Xqoi.c @@ -103,7 +103,7 @@ DLL_EXPORT BOOL API gfpLoadPictureGetInfo( *width = QOIDecoder_GetWidth(qoi); *height = QOIDecoder_GetHeight(qoi); *dpi = 96; - int bytes_per_pixel = QOIDecoder_GetAlpha(qoi) ? 4 : 3; + int bytes_per_pixel = QOIDecoder_HasAlpha(qoi) ? 4 : 3; *bits_per_pixel = bytes_per_pixel << 3; *bytes_per_line = *width * bytes_per_pixel; *has_colormap = FALSE; @@ -117,7 +117,7 @@ DLL_EXPORT BOOL API gfpLoadPictureGetLine(void *ptr, INT line, unsigned char *bu const QOIDecoder *qoi = (const QOIDecoder *) ptr; int width = QOIDecoder_GetWidth(qoi); const int *pixels = QOIDecoder_GetPixels(qoi) + line * width; - int bytes_per_pixel = QOIDecoder_GetAlpha(qoi) ? 4 : 3; + int bytes_per_pixel = QOIDecoder_HasAlpha(qoi) ? 4 : 3; for (int x = 0; x < width; x++) { int rgb = pixels[x]; @@ -203,7 +203,7 @@ DLL_EXPORT void API gfpSavePictureExit(void *ptr) { QOIWriter *w = (QOIWriter *) ptr; QOIEncoder *qoi = QOIEncoder_New(); - if (QOIEncoder_Encode(qoi, w->width, w->height, w->pixels, w->alpha, QOIColorspace_SRGB)) + if (QOIEncoder_Encode(qoi, w->width, w->height, w->pixels, w->alpha, false)) QOIEncoder_SaveStdio(qoi, w->f); else fclose(w->f); diff --git a/file-qoi.c b/file-qoi.c index c7b7286..b836ac3 100644 --- a/file-qoi.c +++ b/file-qoi.c @@ -58,7 +58,7 @@ static gint32 load_image(const gchar *filename) if (image != -1) { gimp_image_set_filename(image, filename); gint32 layer = gimp_layer_new(image, "Background", width, height, - QOIDecoder_GetAlpha(qoi) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, 100, GIMP_NORMAL_MODE); + QOIDecoder_HasAlpha(qoi) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, 100, GIMP_NORMAL_MODE); gimp_image_insert_layer(image, layer, -1, 0); GeglBuffer *buffer = gimp_drawable_get_buffer(layer); gegl_buffer_set(buffer, NULL, 0, get_format(), QOIDecoder_GetPixels(qoi), GEGL_AUTO_ROWSTRIDE); @@ -83,7 +83,7 @@ static bool save_image(const gchar *filename, gint32 image, gint32 drawable) bool alpha = gimp_drawable_has_alpha(drawable); QOIEncoder *qoi = QOIEncoder_New(); - bool ok = QOIEncoder_Encode(qoi, width, height, pixels, alpha, QOIColorspace_SRGB); + bool ok = QOIEncoder_Encode(qoi, width, height, pixels, alpha, false); free(pixels); if (ok) ok = QOIEncoder_SaveFile(qoi, filename); diff --git a/png2qoi.c b/png2qoi.c index 8164384..254b455 100644 --- a/png2qoi.c +++ b/png2qoi.c @@ -75,7 +75,7 @@ static bool png2qoi(const char *input_file, FILE *f) } QOIEncoder *qoi = QOIEncoder_New(); - if (!QOIEncoder_Encode(qoi, png.width, png.height, pixels, alpha, QOIColorspace_SRGB)) { + if (!QOIEncoder_Encode(qoi, png.width, png.height, pixels, alpha, false)) { QOIEncoder_Delete(qoi); free(pixels); fprintf(stderr, "png2qoi: %s: error encoding\n", input_file); diff --git a/transpiled/QOI.c b/transpiled/QOI.c index 3660909..5829ebb 100644 --- a/transpiled/QOI.c +++ b/transpiled/QOI.c @@ -71,7 +71,7 @@ struct QOIDecoder { int height; int *pixels; bool alpha; - int colorspace; + bool linearColorspace; }; static void QOIDecoder_Construct(QOIDecoder *self); static void QOIDecoder_Destruct(QOIDecoder *self); @@ -104,15 +104,15 @@ void QOIEncoder_Delete(QOIEncoder *self) bool QOIEncoder_CanEncode(int width, int height, bool alpha) { - return width > 0 && height > 0 && height <= 2147483629 / width / (alpha ? 5 : 4); + return width > 0 && height > 0 && height <= 2147483625 / width / (alpha ? 5 : 4); } -bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixels, bool alpha, int colorspace) +bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixels, bool alpha, bool linearColorspace) { if (pixels == NULL || !QOIEncoder_CanEncode(width, height, alpha)) return false; int pixelsSize = width * height; - uint8_t *encoded = (uint8_t *) CiShared_Make(14 + pixelsSize * (alpha ? 5 : 4) + 4, sizeof(uint8_t), NULL, NULL); + uint8_t *encoded = (uint8_t *) CiShared_Make(14 + pixelsSize * (alpha ? 5 : 4) + 8, sizeof(uint8_t), NULL, NULL); encoded[0] = 113; encoded[1] = 111; encoded[2] = 105; @@ -126,7 +126,7 @@ bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixel encoded[10] = (uint8_t) (height >> 8); encoded[11] = (uint8_t) height; encoded[12] = (uint8_t) (alpha ? 4 : 3); - encoded[13] = (uint8_t) colorspace; + encoded[13] = (uint8_t) (linearColorspace ? 1 : 0); int index[64] = { 0 }; int encodedOffset = 14; int lastPixel = -16777216; @@ -135,20 +135,18 @@ bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixel int pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= -16777216; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = (uint8_t) (64 + run - 1); - else { - run -= 33; - encoded[encodedOffset++] = (uint8_t) (96 + (run >> 8)); - encoded[encodedOffset++] = (uint8_t) run; + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = (uint8_t) (191 + run); + run = 0; } - run = 0; } - if (pixel != lastPixel) { - int indexOffset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63; + else { + if (run > 0) { + encoded[encodedOffset++] = (uint8_t) (191 + run); + run = 0; + } + int indexOffset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63; if (pixel == index[indexOffset]) encoded[encodedOffset++] = (uint8_t) indexOffset; else { @@ -157,43 +155,45 @@ bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixel int g = pixel >> 8 & 255; int b = pixel & 255; int a = pixel >> 24 & 255; - int dr = r - (lastPixel >> 16 & 255); - int dg = g - (lastPixel >> 8 & 255); - int db = b - (lastPixel & 255); - int da = a - (lastPixel >> 24 & 255); - if (dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15) { - if (da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = (uint8_t) (170 + (dr << 4) + (dg << 2) + db); - else if (da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7) { - encoded[encodedOffset++] = (uint8_t) (208 + dr); - encoded[encodedOffset++] = (uint8_t) (136 + (dg << 4) + db); - } - else { - dr += 16; - encoded[encodedOffset++] = (uint8_t) (224 + (dr >> 1)); - db += 16; - encoded[encodedOffset++] = (uint8_t) (((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3)); - encoded[encodedOffset++] = (uint8_t) (((db & 7) << 5) + da + 16); - } + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = 255; + encoded[encodedOffset + 1] = (uint8_t) r; + encoded[encodedOffset + 2] = (uint8_t) g; + encoded[encodedOffset + 3] = (uint8_t) b; + encoded[encodedOffset + 4] = (uint8_t) a; + encodedOffset += 5; } else { - encoded[encodedOffset++] = (uint8_t) (240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0)); - if (dr != 0) - encoded[encodedOffset++] = (uint8_t) r; - if (dg != 0) - encoded[encodedOffset++] = (uint8_t) g; - if (db != 0) - encoded[encodedOffset++] = (uint8_t) b; - if (da != 0) - encoded[encodedOffset++] = (uint8_t) a; + int dr = r - (lastPixel >> 16 & 255); + int dg = g - (lastPixel >> 8 & 255); + int db = b - (lastPixel & 255); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) + encoded[encodedOffset++] = (uint8_t) (106 + (dr << 4) + (dg << 2) + db); + else { + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7) { + encoded[encodedOffset] = (uint8_t) (160 + dg); + encoded[encodedOffset + 1] = (uint8_t) (136 + (dr << 4) + db); + encodedOffset += 2; + } + else { + encoded[encodedOffset] = 254; + encoded[encodedOffset + 1] = (uint8_t) r; + encoded[encodedOffset + 2] = (uint8_t) g; + encoded[encodedOffset + 3] = (uint8_t) b; + encodedOffset += 4; + } + } } } lastPixel = pixel; } } - memset(encoded + encodedOffset, 0, 4 * sizeof(uint8_t)); + memset(encoded + encodedOffset, 0, 7 * sizeof(uint8_t)); + encoded[encodedOffset + 8 - 1] = 1; CiShared_Assign((void **) &self->encoded, CiShared_AddRef(encoded)); - self->encodedSize = encodedOffset + 4; + self->encodedSize = encodedOffset + 8; CiShared_Release(encoded); return true; } @@ -236,15 +236,35 @@ void QOIDecoder_Delete(QOIDecoder *self) bool QOIDecoder_Decode(QOIDecoder *self, uint8_t const *encoded, int encodedSize) { - if (encoded == NULL || encodedSize < 19 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) + if (encoded == NULL || encodedSize < 23 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) return false; int width = encoded[4] << 24 | encoded[5] << 16 | encoded[6] << 8 | encoded[7]; int height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11]; if (width <= 0 || height <= 0 || height > 2147483647 / width) return false; + switch (encoded[12]) { + case 3: + self->alpha = false; + break; + case 4: + self->alpha = true; + break; + default: + return false; + } + switch (encoded[13]) { + case 0: + self->linearColorspace = false; + break; + case 1: + self->linearColorspace = true; + break; + default: + return false; + } int pixelsSize = width * height; int *pixels = (int *) CiShared_Make(pixelsSize, sizeof(int), NULL, NULL); - encodedSize -= 4; + encodedSize -= 8; int encodedOffset = 14; int index[64] = { 0 }; int pixel = -16777216; @@ -254,49 +274,41 @@ bool QOIDecoder_Decode(QOIDecoder *self, uint8_t const *encoded, int encodedSize return false; } int e = encoded[encodedOffset++]; - if (e < 128) { - if (e < 64) - pixels[pixelsOffset++] = pixel = index[e]; - else { - int run; - if (e < 96) - run = e - 63; - else - run = 33 + ((e - 96) << 8) + encoded[encodedOffset++]; - if (pixelsOffset + run > pixelsSize) { + switch (e >> 6) { + case 0: + pixels[pixelsOffset++] = pixel = index[e]; + continue; + case 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); + break; + case 2: + e -= 160; + int rb = encoded[encodedOffset++]; + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255); + break; + default: + if (e < 254) { + e -= 191; + if (pixelsOffset + e > pixelsSize) { CiShared_Release(pixels); return false; } - for (int _i = 0; _i < run; _i++) + for (int _i = 0; _i < e; _i++) pixels[pixelsOffset + _i] = pixel; - pixelsOffset += run; + pixelsOffset += e; + continue; + } + if (e == 254) { + pixel = (pixel & -16777216) | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 3; } - continue; - } - else if (e < 224) { - if (e < 192) - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); else { - int d = encoded[encodedOffset++]; - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255); + pixel = encoded[encodedOffset + 3] << 24 | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 4; } + break; } - else if (e < 240) { - e = e << 16 | encoded[encodedOffset] << 8 | encoded[encodedOffset + 1]; - encodedOffset += 2; - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255); - } - else { - if ((e & 8) != 0) - pixel = (pixel & -16711681) | encoded[encodedOffset++] << 16; - if ((e & 4) != 0) - pixel = (pixel & -65281) | encoded[encodedOffset++] << 8; - if ((e & 2) != 0) - pixel = (pixel & -256) | encoded[encodedOffset++]; - if ((e & 1) != 0) - pixel = (pixel & 16777215) | encoded[encodedOffset++] << 24; - } - pixels[pixelsOffset++] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel; + pixels[pixelsOffset++] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel; } if (encodedOffset != encodedSize) { CiShared_Release(pixels); @@ -305,8 +317,6 @@ bool QOIDecoder_Decode(QOIDecoder *self, uint8_t const *encoded, int encodedSize self->width = width; self->height = height; CiShared_Assign((void **) &self->pixels, CiShared_AddRef(pixels)); - self->alpha = encoded[12] == 4; - self->colorspace = encoded[13]; CiShared_Release(pixels); return true; } @@ -326,12 +336,12 @@ int const *QOIDecoder_GetPixels(const QOIDecoder *self) return self->pixels; } -bool QOIDecoder_GetAlpha(const QOIDecoder *self) +bool QOIDecoder_HasAlpha(const QOIDecoder *self) { return self->alpha; } -int QOIDecoder_GetColorspace(const QOIDecoder *self) +bool QOIDecoder_IsLinearColorspace(const QOIDecoder *self) { - return self->colorspace; + return self->linearColorspace; } diff --git a/transpiled/QOI.cpp b/transpiled/QOI.cpp index b5fca4b..3dde7ed 100644 --- a/transpiled/QOI.cpp +++ b/transpiled/QOI.cpp @@ -8,15 +8,15 @@ QOIEncoder::QOIEncoder() bool QOIEncoder::canEncode(int width, int height, bool alpha) { - return width > 0 && height > 0 && height <= 2147483629 / width / (alpha ? 5 : 4); + return width > 0 && height > 0 && height <= 2147483625 / width / (alpha ? 5 : 4); } -bool QOIEncoder::encode(int width, int height, int const * pixels, bool alpha, int colorspace) +bool QOIEncoder::encode(int width, int height, int const * pixels, bool alpha, bool linearColorspace) { if (pixels == nullptr || !canEncode(width, height, alpha)) return false; int pixelsSize = width * height; - std::shared_ptr encoded = std::make_shared(14 + pixelsSize * (alpha ? 5 : 4) + 4); + std::shared_ptr encoded = std::make_shared(14 + pixelsSize * (alpha ? 5 : 4) + 8); encoded[0] = 113; encoded[1] = 111; encoded[2] = 105; @@ -30,7 +30,7 @@ bool QOIEncoder::encode(int width, int height, int const * pixels, bool alpha, i encoded[10] = static_cast(height >> 8); encoded[11] = static_cast(height); encoded[12] = static_cast(alpha ? 4 : 3); - encoded[13] = static_cast(colorspace); + encoded[13] = static_cast(linearColorspace ? 1 : 0); std::array index {}; int encodedOffset = 14; int lastPixel = -16777216; @@ -39,20 +39,18 @@ bool QOIEncoder::encode(int width, int height, int const * pixels, bool alpha, i int pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= -16777216; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = static_cast(64 + run - 1); - else { - run -= 33; - encoded[encodedOffset++] = static_cast(96 + (run >> 8)); - encoded[encodedOffset++] = static_cast(run); + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = static_cast(191 + run); + run = 0; } - run = 0; } - if (pixel != lastPixel) { - int indexOffset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63; + else { + if (run > 0) { + encoded[encodedOffset++] = static_cast(191 + run); + run = 0; + } + int indexOffset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63; if (pixel == index[indexOffset]) encoded[encodedOffset++] = static_cast(indexOffset); else { @@ -61,43 +59,45 @@ bool QOIEncoder::encode(int width, int height, int const * pixels, bool alpha, i int g = pixel >> 8 & 255; int b = pixel & 255; int a = pixel >> 24 & 255; - int dr = r - (lastPixel >> 16 & 255); - int dg = g - (lastPixel >> 8 & 255); - int db = b - (lastPixel & 255); - int da = a - (lastPixel >> 24 & 255); - if (dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15) { - if (da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = static_cast(170 + (dr << 4) + (dg << 2) + db); - else if (da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7) { - encoded[encodedOffset++] = static_cast(208 + dr); - encoded[encodedOffset++] = static_cast(136 + (dg << 4) + db); - } - else { - dr += 16; - encoded[encodedOffset++] = static_cast(224 + (dr >> 1)); - db += 16; - encoded[encodedOffset++] = static_cast(((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3)); - encoded[encodedOffset++] = static_cast(((db & 7) << 5) + da + 16); - } + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = 255; + encoded[encodedOffset + 1] = static_cast(r); + encoded[encodedOffset + 2] = static_cast(g); + encoded[encodedOffset + 3] = static_cast(b); + encoded[encodedOffset + 4] = static_cast(a); + encodedOffset += 5; } else { - encoded[encodedOffset++] = static_cast(240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0)); - if (dr != 0) - encoded[encodedOffset++] = static_cast(r); - if (dg != 0) - encoded[encodedOffset++] = static_cast(g); - if (db != 0) - encoded[encodedOffset++] = static_cast(b); - if (da != 0) - encoded[encodedOffset++] = static_cast(a); + int dr = r - (lastPixel >> 16 & 255); + int dg = g - (lastPixel >> 8 & 255); + int db = b - (lastPixel & 255); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) + encoded[encodedOffset++] = static_cast(106 + (dr << 4) + (dg << 2) + db); + else { + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7) { + encoded[encodedOffset] = static_cast(160 + dg); + encoded[encodedOffset + 1] = static_cast(136 + (dr << 4) + db); + encodedOffset += 2; + } + else { + encoded[encodedOffset] = 254; + encoded[encodedOffset + 1] = static_cast(r); + encoded[encodedOffset + 2] = static_cast(g); + encoded[encodedOffset + 3] = static_cast(b); + encodedOffset += 4; + } + } } } lastPixel = pixel; } } - std::fill_n(encoded.get() + encodedOffset, 4, 0); + std::fill_n(encoded.get() + encodedOffset, 7, 0); + encoded[encodedOffset + 8 - 1] = 1; this->encoded = encoded; - this->encodedSize = encodedOffset + 4; + this->encodedSize = encodedOffset + 8; return true; } @@ -116,15 +116,35 @@ QOIDecoder::QOIDecoder() bool QOIDecoder::decode(uint8_t const * encoded, int encodedSize) { - if (encoded == nullptr || encodedSize < 19 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) + if (encoded == nullptr || encodedSize < 23 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) return false; int width = encoded[4] << 24 | encoded[5] << 16 | encoded[6] << 8 | encoded[7]; int height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11]; if (width <= 0 || height <= 0 || height > 2147483647 / width) return false; + switch (encoded[12]) { + case 3: + this->alpha = false; + break; + case 4: + this->alpha = true; + break; + default: + return false; + } + switch (encoded[13]) { + case 0: + this->linearColorspace = false; + break; + case 1: + this->linearColorspace = true; + break; + default: + return false; + } int pixelsSize = width * height; std::shared_ptr pixels = std::make_shared(pixelsSize); - encodedSize -= 4; + encodedSize -= 8; int encodedOffset = 14; std::array index {}; int pixel = -16777216; @@ -132,54 +152,46 @@ bool QOIDecoder::decode(uint8_t const * encoded, int encodedSize) if (encodedOffset >= encodedSize) return false; int e = encoded[encodedOffset++]; - if (e < 128) { - if (e < 64) - pixels[pixelsOffset++] = pixel = index[e]; - else { - int run; - if (e < 96) - run = e - 63; - else - run = 33 + ((e - 96) << 8) + encoded[encodedOffset++]; - if (pixelsOffset + run > pixelsSize) + switch (e >> 6) { + case 0: + pixels[pixelsOffset++] = pixel = index[e]; + continue; + case 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); + break; + case 2: + e -= 160; + { + int rb = encoded[encodedOffset++]; + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255); + break; + } + default: + if (e < 254) { + e -= 191; + if (pixelsOffset + e > pixelsSize) return false; - std::fill_n(pixels.get() + pixelsOffset, run, pixel); - pixelsOffset += run; + std::fill_n(pixels.get() + pixelsOffset, e, pixel); + pixelsOffset += e; + continue; + } + if (e == 254) { + pixel = (pixel & -16777216) | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 3; } - continue; - } - else if (e < 224) { - if (e < 192) - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); else { - int d = encoded[encodedOffset++]; - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255); + pixel = encoded[encodedOffset + 3] << 24 | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 4; } + break; } - else if (e < 240) { - e = e << 16 | encoded[encodedOffset] << 8 | encoded[encodedOffset + 1]; - encodedOffset += 2; - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255); - } - else { - if ((e & 8) != 0) - pixel = (pixel & -16711681) | encoded[encodedOffset++] << 16; - if ((e & 4) != 0) - pixel = (pixel & -65281) | encoded[encodedOffset++] << 8; - if ((e & 2) != 0) - pixel = (pixel & -256) | encoded[encodedOffset++]; - if ((e & 1) != 0) - pixel = (pixel & 16777215) | encoded[encodedOffset++] << 24; - } - pixels[pixelsOffset++] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel; + pixels[pixelsOffset++] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel; } if (encodedOffset != encodedSize) return false; this->width = width; this->height = height; this->pixels = pixels; - this->alpha = encoded[12] == 4; - this->colorspace = encoded[13]; return true; } @@ -198,12 +210,12 @@ int const * QOIDecoder::getPixels() const return this->pixels.get(); } -bool QOIDecoder::getAlpha() const +bool QOIDecoder::hasAlpha() const { return this->alpha; } -int QOIDecoder::getColorspace() const +bool QOIDecoder::isLinearColorspace() const { - return this->colorspace; + return this->linearColorspace; } diff --git a/transpiled/QOI.cs b/transpiled/QOI.cs index d546c06..1004f08 100644 --- a/transpiled/QOI.cs +++ b/transpiled/QOI.cs @@ -1,21 +1,6 @@ // Generated automatically with "cito". Do not edit. using System; -/// QOI color space metadata. -/// Saved in the file header, but doesn't affect encoding or decoding in any way. -public static class QOIColorspace -{ - - /// sRGBA. - public const int Srgb = 0; - - /// sRGB with linear alpha. - public const int SrgbLinearAlpha = 1; - - /// Linear RGBA. - public const int Linear = 15; -} - /// Encoder of the "Quite OK Image" (QOI) format. /// Losslessly compresses an image to a byte array. public class QOIEncoder @@ -28,7 +13,7 @@ public QOIEncoder() internal const int HeaderSize = 14; - internal const int PaddingSize = 4; + internal const int PaddingSize = 8; byte[] Encoded; @@ -40,7 +25,7 @@ public QOIEncoder() /// Whether the image has the alpha channel (transparency). public static bool CanEncode(int width, int height, bool alpha) { - return width > 0 && height > 0 && height <= 2147483629 / width / (alpha ? 5 : 4); + return width > 0 && height > 0 && height <= 2147483625 / width / (alpha ? 5 : 4); } /// Encodes the given image. @@ -49,13 +34,13 @@ public static bool CanEncode(int width, int height, bool alpha) /// Image height in pixels. /// Pixels of the image, top-down, left-to-right. /// specifies that all pixels are opaque. High bytes of pixels elements are ignored then. - /// Specifies the color space. See QOIColorspace. - public bool Encode(int width, int height, int[] pixels, bool alpha, int colorspace) + /// Specifies the color space. + public bool Encode(int width, int height, int[] pixels, bool alpha, bool linearColorspace) { if (pixels == null || !CanEncode(width, height, alpha)) return false; int pixelsSize = width * height; - byte[] encoded = new byte[14 + pixelsSize * (alpha ? 5 : 4) + 4]; + byte[] encoded = new byte[14 + pixelsSize * (alpha ? 5 : 4) + 8]; encoded[0] = 113; encoded[1] = 111; encoded[2] = 105; @@ -69,7 +54,7 @@ public bool Encode(int width, int height, int[] pixels, bool alpha, int colorspa encoded[10] = (byte) (height >> 8); encoded[11] = (byte) height; encoded[12] = (byte) (alpha ? 4 : 3); - encoded[13] = (byte) colorspace; + encoded[13] = (byte) (linearColorspace ? 1 : 0); int[] index = new int[64]; int encodedOffset = 14; int lastPixel = -16777216; @@ -78,20 +63,18 @@ public bool Encode(int width, int height, int[] pixels, bool alpha, int colorspa int pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= -16777216; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = (byte) (64 + run - 1); - else { - run -= 33; - encoded[encodedOffset++] = (byte) (96 + (run >> 8)); - encoded[encodedOffset++] = (byte) run; + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = (byte) (191 + run); + run = 0; } - run = 0; } - if (pixel != lastPixel) { - int indexOffset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63; + else { + if (run > 0) { + encoded[encodedOffset++] = (byte) (191 + run); + run = 0; + } + int indexOffset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63; if (pixel == index[indexOffset]) encoded[encodedOffset++] = (byte) indexOffset; else { @@ -100,43 +83,45 @@ public bool Encode(int width, int height, int[] pixels, bool alpha, int colorspa int g = pixel >> 8 & 255; int b = pixel & 255; int a = pixel >> 24 & 255; - int dr = r - (lastPixel >> 16 & 255); - int dg = g - (lastPixel >> 8 & 255); - int db = b - (lastPixel & 255); - int da = a - (lastPixel >> 24 & 255); - if (dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15) { - if (da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = (byte) (170 + (dr << 4) + (dg << 2) + db); - else if (da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7) { - encoded[encodedOffset++] = (byte) (208 + dr); - encoded[encodedOffset++] = (byte) (136 + (dg << 4) + db); - } - else { - dr += 16; - encoded[encodedOffset++] = (byte) (224 + (dr >> 1)); - db += 16; - encoded[encodedOffset++] = (byte) (((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3)); - encoded[encodedOffset++] = (byte) (((db & 7) << 5) + da + 16); - } + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = 255; + encoded[encodedOffset + 1] = (byte) r; + encoded[encodedOffset + 2] = (byte) g; + encoded[encodedOffset + 3] = (byte) b; + encoded[encodedOffset + 4] = (byte) a; + encodedOffset += 5; } else { - encoded[encodedOffset++] = (byte) (240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0)); - if (dr != 0) - encoded[encodedOffset++] = (byte) r; - if (dg != 0) - encoded[encodedOffset++] = (byte) g; - if (db != 0) - encoded[encodedOffset++] = (byte) b; - if (da != 0) - encoded[encodedOffset++] = (byte) a; + int dr = r - (lastPixel >> 16 & 255); + int dg = g - (lastPixel >> 8 & 255); + int db = b - (lastPixel & 255); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) + encoded[encodedOffset++] = (byte) (106 + (dr << 4) + (dg << 2) + db); + else { + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7) { + encoded[encodedOffset] = (byte) (160 + dg); + encoded[encodedOffset + 1] = (byte) (136 + (dr << 4) + db); + encodedOffset += 2; + } + else { + encoded[encodedOffset] = 254; + encoded[encodedOffset + 1] = (byte) r; + encoded[encodedOffset + 2] = (byte) g; + encoded[encodedOffset + 3] = (byte) b; + encodedOffset += 4; + } + } } } lastPixel = pixel; } } - Array.Clear(encoded, encodedOffset, 4); + Array.Clear(encoded, encodedOffset, 7); + encoded[encodedOffset + 8 - 1] = 1; this.Encoded = encoded; - this.EncodedSize = encodedOffset + 4; + this.EncodedSize = encodedOffset + 8; return true; } @@ -173,7 +158,7 @@ public QOIDecoder() bool Alpha; - int Colorspace; + bool LinearColorspace; /// Decodes the given QOI file contents. /// Returns if decoded successfully. @@ -181,15 +166,35 @@ public QOIDecoder() /// QOI file length. public bool Decode(byte[] encoded, int encodedSize) { - if (encoded == null || encodedSize < 19 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) + if (encoded == null || encodedSize < 23 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) return false; int width = encoded[4] << 24 | encoded[5] << 16 | encoded[6] << 8 | encoded[7]; int height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11]; if (width <= 0 || height <= 0 || height > 2147483647 / width) return false; + switch (encoded[12]) { + case 3: + this.Alpha = false; + break; + case 4: + this.Alpha = true; + break; + default: + return false; + } + switch (encoded[13]) { + case 0: + this.LinearColorspace = false; + break; + case 1: + this.LinearColorspace = true; + break; + default: + return false; + } int pixelsSize = width * height; int[] pixels = new int[pixelsSize]; - encodedSize -= 4; + encodedSize -= 8; int encodedOffset = 14; int[] index = new int[64]; int pixel = -16777216; @@ -197,54 +202,44 @@ public bool Decode(byte[] encoded, int encodedSize) if (encodedOffset >= encodedSize) return false; int e = encoded[encodedOffset++]; - if (e < 128) { - if (e < 64) - pixels[pixelsOffset++] = pixel = index[e]; - else { - int run; - if (e < 96) - run = e - 63; - else - run = 33 + ((e - 96) << 8) + encoded[encodedOffset++]; - if (pixelsOffset + run > pixelsSize) + switch (e >> 6) { + case 0: + pixels[pixelsOffset++] = pixel = index[e]; + continue; + case 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); + break; + case 2: + e -= 160; + int rb = encoded[encodedOffset++]; + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255); + break; + default: + if (e < 254) { + e -= 191; + if (pixelsOffset + e > pixelsSize) return false; - Array.Fill(pixels, pixel, pixelsOffset, run); - pixelsOffset += run; + Array.Fill(pixels, pixel, pixelsOffset, e); + pixelsOffset += e; + continue; + } + if (e == 254) { + pixel = (pixel & -16777216) | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 3; } - continue; - } - else if (e < 224) { - if (e < 192) - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); else { - int d = encoded[encodedOffset++]; - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255); + pixel = encoded[encodedOffset + 3] << 24 | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 4; } + break; } - else if (e < 240) { - e = e << 16 | encoded[encodedOffset] << 8 | encoded[encodedOffset + 1]; - encodedOffset += 2; - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255); - } - else { - if ((e & 8) != 0) - pixel = (pixel & -16711681) | encoded[encodedOffset++] << 16; - if ((e & 4) != 0) - pixel = (pixel & -65281) | encoded[encodedOffset++] << 8; - if ((e & 2) != 0) - pixel = (pixel & -256) | encoded[encodedOffset++]; - if ((e & 1) != 0) - pixel = (pixel & 16777215) | encoded[encodedOffset++] << 24; - } - pixels[pixelsOffset++] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel; + pixels[pixelsOffset++] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel; } if (encodedOffset != encodedSize) return false; this.Width = width; this.Height = height; this.Pixels = pixels; - this.Alpha = encoded[12] == 4; - this.Colorspace = encoded[13]; return true; } @@ -268,15 +263,15 @@ public int[] GetPixels() } /// Returns the information about the alpha channel from the file header. - public bool GetAlpha() + public bool HasAlpha() { return this.Alpha; } /// Returns the color space information from the file header. - /// See QOIColorspace. - public int GetColorspace() + /// = sRGB with linear alpha channel. = all channels linear. + public bool IsLinearColorspace() { - return this.Colorspace; + return this.LinearColorspace; } } diff --git a/transpiled/QOI.h b/transpiled/QOI.h index 9d93a82..4e9045b 100644 --- a/transpiled/QOI.h +++ b/transpiled/QOI.h @@ -8,21 +8,6 @@ extern "C" { typedef struct QOIEncoder QOIEncoder; typedef struct QOIDecoder QOIDecoder; -/** - * sRGBA. - */ -#define QOIColorspace_SRGB 0 - -/** - * sRGB with linear alpha. - */ -#define QOIColorspace_SRGB_LINEAR_ALPHA 1 - -/** - * Linear RGBA. - */ -#define QOIColorspace_LINEAR 15 - QOIEncoder *QOIEncoder_New(void); void QOIEncoder_Delete(QOIEncoder *self); @@ -42,9 +27,9 @@ bool QOIEncoder_CanEncode(int width, int height, bool alpha); * @param height Image height in pixels. * @param pixels Pixels of the image, top-down, left-to-right. * @param alpha false specifies that all pixels are opaque. High bytes of pixels elements are ignored then. - * @param colorspace Specifies the color space. See QOIColorspace. + * @param linearColorspace Specifies the color space. */ -bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixels, bool alpha, int colorspace); +bool QOIEncoder_Encode(QOIEncoder *self, int width, int height, int const *pixels, bool alpha, bool linearColorspace); /** * Returns the encoded file contents. @@ -96,14 +81,14 @@ int const *QOIDecoder_GetPixels(const QOIDecoder *self); * Returns the information about the alpha channel from the file header. * @param self This QOIDecoder. */ -bool QOIDecoder_GetAlpha(const QOIDecoder *self); +bool QOIDecoder_HasAlpha(const QOIDecoder *self); /** * Returns the color space information from the file header. - * See QOIColorspace. + * false = sRGB with linear alpha channel.true = all channels linear. * @param self This QOIDecoder. */ -int QOIDecoder_GetColorspace(const QOIDecoder *self); +bool QOIDecoder_IsLinearColorspace(const QOIDecoder *self); #ifdef __cplusplus } diff --git a/transpiled/QOI.hpp b/transpiled/QOI.hpp index ad57a9b..4fce20c 100644 --- a/transpiled/QOI.hpp +++ b/transpiled/QOI.hpp @@ -2,33 +2,9 @@ #pragma once #include #include -class QOIColorspace; class QOIEncoder; class QOIDecoder; -/** - * QOI color space metadata. - * Saved in the file header, but doesn't affect encoding or decoding in any way. - */ -class QOIColorspace -{ -public: - /** - * sRGBA. - */ - static constexpr int srgb = 0; - /** - * sRGB with linear alpha. - */ - static constexpr int srgbLinearAlpha = 1; - /** - * Linear RGBA. - */ - static constexpr int linear = 15; -private: - QOIColorspace() = delete; -}; - /** * Encoder of the "Quite OK Image" (QOI) format. * Losslessly compresses an image to a byte array. @@ -55,9 +31,9 @@ class QOIEncoder * @param height Image height in pixels. * @param pixels Pixels of the image, top-down, left-to-right. * @param alpha false specifies that all pixels are opaque. High bytes of pixels elements are ignored then. - * @param colorspace Specifies the color space. See QOIColorspace. + * @param linearColorspace Specifies the color space. */ - bool encode(int width, int height, int const * pixels, bool alpha, int colorspace); + bool encode(int width, int height, int const * pixels, bool alpha, bool linearColorspace); /** * Returns the encoded file contents. * This method can only be called after Encode returned true. @@ -71,7 +47,7 @@ class QOIEncoder int getEncodedSize() const; public: static constexpr int headerSize = 14; - static constexpr int paddingSize = 4; + static constexpr int paddingSize = 8; private: std::shared_ptr encoded; int encodedSize; @@ -111,16 +87,16 @@ class QOIDecoder /** * Returns the information about the alpha channel from the file header. */ - bool getAlpha() const; + bool hasAlpha() const; /** * Returns the color space information from the file header. - * See QOIColorspace. + * false = sRGB with linear alpha channel.true = all channels linear. */ - int getColorspace() const; + bool isLinearColorspace() const; private: int width; int height; std::shared_ptr pixels; bool alpha; - int colorspace; + bool linearColorspace; }; diff --git a/transpiled/QOI.js b/transpiled/QOI.js index 05637c1..74a1b70 100644 --- a/transpiled/QOI.js +++ b/transpiled/QOI.js @@ -2,29 +2,6 @@ "use strict"; -/** - * QOI color space metadata. - * Saved in the file header, but doesn't affect encoding or decoding in any way. - */ -function QOIColorspace() -{ -} - -/** - * sRGBA. - */ -QOIColorspace.SRGB = 0; - -/** - * sRGB with linear alpha. - */ -QOIColorspace.SRGB_LINEAR_ALPHA = 1; - -/** - * Linear RGBA. - */ -QOIColorspace.LINEAR = 15; - /** * Encoder of the "Quite OK Image" (QOI) format. * Losslessly compresses an image to a byte array. @@ -35,7 +12,7 @@ function QOIEncoder() QOIEncoder.HEADER_SIZE = 14; -QOIEncoder.PADDING_SIZE = 4; +QOIEncoder.PADDING_SIZE = 8; /** * Determines if an image of given size can be encoded. @@ -45,7 +22,7 @@ QOIEncoder.PADDING_SIZE = 4; */ QOIEncoder.canEncode = function(width, height, alpha) { - return width > 0 && height > 0 && height <= ((2147483629 / width | 0) / (alpha ? 5 : 4) | 0); + return width > 0 && height > 0 && height <= ((2147483625 / width | 0) / (alpha ? 5 : 4) | 0); } /** @@ -55,14 +32,14 @@ QOIEncoder.canEncode = function(width, height, alpha) * @param height Image height in pixels. * @param pixels Pixels of the image, top-down, left-to-right. * @param alpha false specifies that all pixels are opaque. High bytes of pixels elements are ignored then. - * @param colorspace Specifies the color space. See QOIColorspace. + * @param linearColorspace Specifies the color space. */ -QOIEncoder.prototype.encode = function(width, height, pixels, alpha, colorspace) +QOIEncoder.prototype.encode = function(width, height, pixels, alpha, linearColorspace) { if (pixels == null || !QOIEncoder.canEncode(width, height, alpha)) return false; let pixelsSize = width * height; - let encoded = new Uint8Array(14 + pixelsSize * (alpha ? 5 : 4) + 4); + let encoded = new Uint8Array(14 + pixelsSize * (alpha ? 5 : 4) + 8); encoded[0] = 113; encoded[1] = 111; encoded[2] = 105; @@ -76,7 +53,7 @@ QOIEncoder.prototype.encode = function(width, height, pixels, alpha, colorspace) encoded[10] = height >> 8 & 255; encoded[11] = height & 255; encoded[12] = alpha ? 4 : 3; - encoded[13] = colorspace; + encoded[13] = linearColorspace ? 1 : 0; const index = new Int32Array(64); let encodedOffset = 14; let lastPixel = -16777216; @@ -85,20 +62,18 @@ QOIEncoder.prototype.encode = function(width, height, pixels, alpha, colorspace) let pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= -16777216; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = 64 + run - 1; - else { - run -= 33; - encoded[encodedOffset++] = 96 + (run >> 8); - encoded[encodedOffset++] = run & 255; + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = 191 + run; + run = 0; } - run = 0; } - if (pixel != lastPixel) { - let indexOffset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63; + else { + if (run > 0) { + encoded[encodedOffset++] = 191 + run; + run = 0; + } + let indexOffset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63; if (pixel == index[indexOffset]) encoded[encodedOffset++] = indexOffset; else { @@ -107,43 +82,45 @@ QOIEncoder.prototype.encode = function(width, height, pixels, alpha, colorspace) let g = pixel >> 8 & 255; let b = pixel & 255; let a = pixel >> 24 & 255; - let dr = r - (lastPixel >> 16 & 255); - let dg = g - (lastPixel >> 8 & 255); - let db = b - (lastPixel & 255); - let da = a - (lastPixel >> 24 & 255); - if (dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15) { - if (da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = 170 + (dr << 4) + (dg << 2) + db; - else if (da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7) { - encoded[encodedOffset++] = 208 + dr; - encoded[encodedOffset++] = 136 + (dg << 4) + db; - } - else { - dr += 16; - encoded[encodedOffset++] = 224 + (dr >> 1); - db += 16; - encoded[encodedOffset++] = ((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3); - encoded[encodedOffset++] = ((db & 7) << 5) + da + 16; - } + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = 255; + encoded[encodedOffset + 1] = r; + encoded[encodedOffset + 2] = g; + encoded[encodedOffset + 3] = b; + encoded[encodedOffset + 4] = a; + encodedOffset += 5; } else { - encoded[encodedOffset++] = 240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0); - if (dr != 0) - encoded[encodedOffset++] = r; - if (dg != 0) - encoded[encodedOffset++] = g; - if (db != 0) - encoded[encodedOffset++] = b; - if (da != 0) - encoded[encodedOffset++] = a; + let dr = r - (lastPixel >> 16 & 255); + let dg = g - (lastPixel >> 8 & 255); + let db = b - (lastPixel & 255); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) + encoded[encodedOffset++] = 106 + (dr << 4) + (dg << 2) + db; + else { + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7) { + encoded[encodedOffset] = 160 + dg; + encoded[encodedOffset + 1] = 136 + (dr << 4) + db; + encodedOffset += 2; + } + else { + encoded[encodedOffset] = 254; + encoded[encodedOffset + 1] = r; + encoded[encodedOffset + 2] = g; + encoded[encodedOffset + 3] = b; + encodedOffset += 4; + } + } } } lastPixel = pixel; } } - encoded.fill(0, encodedOffset, encodedOffset + 4); + encoded.fill(0, encodedOffset, encodedOffset + 7); + encoded[encodedOffset + 8 - 1] = 1; this.encoded = encoded; - this.encodedSize = encodedOffset + 4; + this.encodedSize = encodedOffset + 8; return true; } @@ -181,15 +158,35 @@ function QOIDecoder() */ QOIDecoder.prototype.decode = function(encoded, encodedSize) { - if (encoded == null || encodedSize < 19 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) + if (encoded == null || encodedSize < 23 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) return false; let width = encoded[4] << 24 | encoded[5] << 16 | encoded[6] << 8 | encoded[7]; let height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11]; if (width <= 0 || height <= 0 || height > (2147483647 / width | 0)) return false; + switch (encoded[12]) { + case 3: + this.alpha = false; + break; + case 4: + this.alpha = true; + break; + default: + return false; + } + switch (encoded[13]) { + case 0: + this.linearColorspace = false; + break; + case 1: + this.linearColorspace = true; + break; + default: + return false; + } let pixelsSize = width * height; let pixels = new Int32Array(pixelsSize); - encodedSize -= 4; + encodedSize -= 8; let encodedOffset = 14; const index = new Int32Array(64); let pixel = -16777216; @@ -197,54 +194,44 @@ QOIDecoder.prototype.decode = function(encoded, encodedSize) if (encodedOffset >= encodedSize) return false; let e = encoded[encodedOffset++]; - if (e < 128) { - if (e < 64) - pixels[pixelsOffset++] = pixel = index[e]; - else { - let run; - if (e < 96) - run = e - 63; - else - run = 33 + ((e - 96) << 8) + encoded[encodedOffset++]; - if (pixelsOffset + run > pixelsSize) + switch (e >> 6) { + case 0: + pixels[pixelsOffset++] = pixel = index[e]; + continue; + case 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); + break; + case 2: + e -= 160; + let rb = encoded[encodedOffset++]; + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255); + break; + default: + if (e < 254) { + e -= 191; + if (pixelsOffset + e > pixelsSize) return false; - pixels.fill(pixel, pixelsOffset, pixelsOffset + run); - pixelsOffset += run; + pixels.fill(pixel, pixelsOffset, pixelsOffset + e); + pixelsOffset += e; + continue; + } + if (e == 254) { + pixel = (pixel & -16777216) | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 3; } - continue; - } - else if (e < 224) { - if (e < 192) - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); else { - let d = encoded[encodedOffset++]; - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255); + pixel = encoded[encodedOffset + 3] << 24 | encoded[encodedOffset] << 16 | encoded[encodedOffset + 1] << 8 | encoded[encodedOffset + 2]; + encodedOffset += 4; } + break; } - else if (e < 240) { - e = e << 16 | encoded[encodedOffset] << 8 | encoded[encodedOffset + 1]; - encodedOffset += 2; - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255); - } - else { - if ((e & 8) != 0) - pixel = (pixel & -16711681) | encoded[encodedOffset++] << 16; - if ((e & 4) != 0) - pixel = (pixel & -65281) | encoded[encodedOffset++] << 8; - if ((e & 2) != 0) - pixel = (pixel & -256) | encoded[encodedOffset++]; - if ((e & 1) != 0) - pixel = (pixel & 16777215) | encoded[encodedOffset++] << 24; - } - pixels[pixelsOffset++] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel; + pixels[pixelsOffset++] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel; } if (encodedOffset != encodedSize) return false; this.width = width; this.height = height; this.pixels = pixels; - this.alpha = encoded[12] == 4; - this.colorspace = encoded[13]; return true; } @@ -276,16 +263,16 @@ QOIDecoder.prototype.getPixels = function() /** * Returns the information about the alpha channel from the file header. */ -QOIDecoder.prototype.getAlpha = function() +QOIDecoder.prototype.hasAlpha = function() { return this.alpha; } /** * Returns the color space information from the file header. - * See QOIColorspace. + * false = sRGB with linear alpha channel.true = all channels linear. */ -QOIDecoder.prototype.getColorspace = function() +QOIDecoder.prototype.isLinearColorspace = function() { - return this.colorspace; + return this.linearColorspace; } diff --git a/transpiled/QOI.py b/transpiled/QOI.py index 9fb9233..f01c94f 100644 --- a/transpiled/QOI.py +++ b/transpiled/QOI.py @@ -1,20 +1,6 @@ # Generated automatically with "cito". Do not edit. import array -class QOIColorspace: - """QOI color space metadata. - - Saved in the file header, but doesn't affect encoding or decoding in any way.""" - - SRGB = 0 - """sRGBA.""" - - SRGB_LINEAR_ALPHA = 1 - """sRGB with linear alpha.""" - - LINEAR = 15 - """Linear RGBA.""" - class QOIEncoder: """Encoder of the "Quite OK Image" (QOI) format. @@ -22,7 +8,7 @@ class QOIEncoder: _HEADER_SIZE = 14 - _PADDING_SIZE = 4 + _PADDING_SIZE = 8 def __init__(self): """Constructs the encoder. @@ -37,9 +23,9 @@ def can_encode(width, height, alpha): :param height: Image height in pixels. :param alpha: Whether the image has the alpha channel (transparency). """ - return width > 0 and height > 0 and height <= int(int(2147483629 / width) / (5 if alpha else 4)) + return width > 0 and height > 0 and height <= int(int(2147483625 / width) / (5 if alpha else 4)) - def encode(self, width, height, pixels, alpha, colorspace): + def encode(self, width, height, pixels, alpha, linear_colorspace): """Encodes the given image. Returns `true` if encoded successfully. @@ -48,12 +34,12 @@ def encode(self, width, height, pixels, alpha, colorspace): :param height: Image height in pixels. :param pixels: Pixels of the image, top-down, left-to-right. :param alpha: `false` specifies that all pixels are opaque. High bytes of `pixels` elements are ignored then. - :param colorspace: Specifies the color space. See `QOIColorspace`. + :param linear_colorspace: Specifies the color space. """ if pixels is None or not QOIEncoder.can_encode(width, height, alpha): return False pixels_size = width * height - encoded = bytearray(14 + pixels_size * (5 if alpha else 4) + 4) + encoded = bytearray(14 + pixels_size * (5 if alpha else 4) + 8) encoded[0] = 113 encoded[1] = 111 encoded[2] = 105 @@ -67,7 +53,7 @@ def encode(self, width, height, pixels, alpha, colorspace): encoded[10] = height >> 8 & 255 encoded[11] = height & 255 encoded[12] = 4 if alpha else 3 - encoded[13] = colorspace + encoded[13] = 1 if linear_colorspace else 0 index = array.array("i", [ 0 ]) * 64 encoded_offset = 14 last_pixel = -16777216 @@ -80,19 +66,16 @@ def encode(self, width, height, pixels, alpha, colorspace): pixel |= -16777216 if pixel == last_pixel: run += 1 - if run > 0 and (pixel != last_pixel or run == 8224 or pixels_offset >= pixels_size): - if run <= 32: - encoded[encoded_offset] = 64 + run - 1 - encoded_offset += 1 - else: - run -= 33 - encoded[encoded_offset] = 96 + (run >> 8) + if run == 62 or pixels_offset >= pixels_size: + encoded[encoded_offset] = 191 + run encoded_offset += 1 - encoded[encoded_offset] = run & 255 + run = 0 + else: + if run > 0: + encoded[encoded_offset] = 191 + run encoded_offset += 1 - run = 0 - if pixel != last_pixel: - index_offset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63 + run = 0 + index_offset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63 if pixel == index[index_offset]: encoded[encoded_offset] = index_offset encoded_offset += 1 @@ -102,47 +85,38 @@ def encode(self, width, height, pixels, alpha, colorspace): g = pixel >> 8 & 255 b = pixel & 255 a = pixel >> 24 & 255 - dr = r - (last_pixel >> 16 & 255) - dg = g - (last_pixel >> 8 & 255) - db = b - (last_pixel & 255) - da = a - (last_pixel >> 24 & 255) - if dr >= -16 and dr <= 15 and dg >= -16 and dg <= 15 and db >= -16 and db <= 15 and da >= -16 and da <= 15: - if da == 0 and dr >= -2 and dr <= 1 and dg >= -2 and dg <= 1 and db >= -2 and db <= 1: - encoded[encoded_offset] = 170 + (dr << 4) + (dg << 2) + db - encoded_offset += 1 - elif da == 0 and dg >= -8 and dg <= 7 and db >= -8 and db <= 7: - encoded[encoded_offset] = 208 + dr - encoded_offset += 1 - encoded[encoded_offset] = 136 + (dg << 4) + db - encoded_offset += 1 - else: - dr += 16 - encoded[encoded_offset] = 224 + (dr >> 1) - encoded_offset += 1 - db += 16 - encoded[encoded_offset] = ((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3) - encoded_offset += 1 - encoded[encoded_offset] = ((db & 7) << 5) + da + 16 - encoded_offset += 1 + if (pixel ^ last_pixel) >> 24 != 0: + encoded[encoded_offset] = 255 + encoded[encoded_offset + 1] = r + encoded[encoded_offset + 2] = g + encoded[encoded_offset + 3] = b + encoded[encoded_offset + 4] = a + encoded_offset += 5 else: - encoded[encoded_offset] = 240 | (8 if dr != 0 else 0) | (4 if dg != 0 else 0) | (2 if db != 0 else 0) | (1 if da != 0 else 0) - encoded_offset += 1 - if dr != 0: - encoded[encoded_offset] = r - encoded_offset += 1 - if dg != 0: - encoded[encoded_offset] = g - encoded_offset += 1 - if db != 0: - encoded[encoded_offset] = b - encoded_offset += 1 - if da != 0: - encoded[encoded_offset] = a + dr = r - (last_pixel >> 16 & 255) + dg = g - (last_pixel >> 8 & 255) + db = b - (last_pixel & 255) + if dr >= -2 and dr <= 1 and dg >= -2 and dg <= 1 and db >= -2 and db <= 1: + encoded[encoded_offset] = 106 + (dr << 4) + (dg << 2) + db encoded_offset += 1 + else: + dr -= dg + db -= dg + if dr >= -8 and dr <= 7 and dg >= -32 and dg <= 31 and db >= -8 and db <= 7: + encoded[encoded_offset] = 160 + dg + encoded[encoded_offset + 1] = 136 + (dr << 4) + db + encoded_offset += 2 + else: + encoded[encoded_offset] = 254 + encoded[encoded_offset + 1] = r + encoded[encoded_offset + 2] = g + encoded[encoded_offset + 3] = b + encoded_offset += 4 last_pixel = pixel - encoded[encoded_offset:encoded_offset + 4] = bytearray(4) + encoded[encoded_offset:encoded_offset + 7] = bytearray(7) + encoded[encoded_offset + 8 - 1] = 1 self._encoded = encoded - self._encoded_size = encoded_offset + 4 + self._encoded_size = encoded_offset + 8 return True def get_encoded(self): @@ -173,15 +147,27 @@ def decode(self, encoded, encoded_size): :param encoded: QOI file contents. Only the first `encodedSize` bytes are accessed. :param encoded_size: QOI file length. """ - if encoded is None or encoded_size < 19 or encoded[0] != 113 or encoded[1] != 111 or encoded[2] != 105 or encoded[3] != 102: + if encoded is None or encoded_size < 23 or encoded[0] != 113 or encoded[1] != 111 or encoded[2] != 105 or encoded[3] != 102: return False width = encoded[4] << 24 | encoded[5] << 16 | encoded[6] << 8 | encoded[7] height = encoded[8] << 24 | encoded[9] << 16 | encoded[10] << 8 | encoded[11] if width <= 0 or height <= 0 or height > int(2147483647 / width): return False + if encoded[12] == 3: + self._alpha = False + elif encoded[12] == 4: + self._alpha = True + else: + return False + if encoded[13] == 0: + self._linear_colorspace = False + elif encoded[13] == 1: + self._linear_colorspace = True + else: + return False pixels_size = width * height pixels = array.array("i", [ 0 ]) * pixels_size - encoded_size -= 4 + encoded_size -= 8 encoded_offset = 14 index = array.array("i", [ 0 ]) * 64 pixel = -16777216 @@ -191,54 +177,39 @@ def decode(self, encoded, encoded_size): return False e = encoded[encoded_offset] encoded_offset += 1 - if e < 128: - if e < 64: - pixels[pixels_offset] = pixel = index[e] - pixels_offset += 1 - else: - if e < 96: - run = e - 63 - else: - run = 33 + ((e - 96) << 8) + encoded[encoded_offset] - encoded_offset += 1 - if pixels_offset + run > pixels_size: - return False - pixels[pixels_offset:pixels_offset + run] = array.array("i", [ pixel ]) * run - pixels_offset += run + ci_switch_tmp = e >> 6 + if ci_switch_tmp == 0: + pixels[pixels_offset] = pixel = index[e] + pixels_offset += 1 continue - elif e < 224: - if e < 192: - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255) - else: - d = encoded[encoded_offset] - encoded_offset += 1 - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255) - elif e < 240: - e = e << 16 | encoded[encoded_offset] << 8 | encoded[encoded_offset + 1] - encoded_offset += 2 - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255) + elif ci_switch_tmp == 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255) + elif ci_switch_tmp == 2: + e -= 160 + rb = encoded[encoded_offset] + encoded_offset += 1 + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255) else: - if (e & 8) != 0: - pixel = (pixel & -16711681) | encoded[encoded_offset] << 16 - encoded_offset += 1 - if (e & 4) != 0: - pixel = (pixel & -65281) | encoded[encoded_offset] << 8 - encoded_offset += 1 - if (e & 2) != 0: - pixel = (pixel & -256) | encoded[encoded_offset] - encoded_offset += 1 - if (e & 1) != 0: - pixel = (pixel & 16777215) | encoded[encoded_offset] << 24 - encoded_offset += 1 - pixels[pixels_offset] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel + if e < 254: + e -= 191 + if pixels_offset + e > pixels_size: + return False + pixels[pixels_offset:pixels_offset + e] = array.array("i", [ pixel ]) * e + pixels_offset += e + continue + if e == 254: + pixel = (pixel & -16777216) | encoded[encoded_offset] << 16 | encoded[encoded_offset + 1] << 8 | encoded[encoded_offset + 2] + encoded_offset += 3 + else: + pixel = encoded[encoded_offset + 3] << 24 | encoded[encoded_offset] << 16 | encoded[encoded_offset + 1] << 8 | encoded[encoded_offset + 2] + encoded_offset += 4 + pixels[pixels_offset] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel pixels_offset += 1 if encoded_offset != encoded_size: return False self._width = width self._height = height self._pixels = pixels - self._alpha = encoded[12] == 4 - self._colorspace = encoded[13] return True def get_width(self): @@ -255,12 +226,12 @@ def get_pixels(self): Each pixel is a 32-bit integer 0xAARRGGBB.""" return self._pixels - def get_alpha(self): + def has_alpha(self): """Returns the information about the alpha channel from the file header.""" return self._alpha - def get_colorspace(self): + def is_linear_colorspace(self): """Returns the color space information from the file header. - See `QOIColorspace`.""" - return self._colorspace + `false` = sRGB with linear alpha channel.`true` = all channels linear.""" + return self._linear_colorspace diff --git a/transpiled/QOI.swift b/transpiled/QOI.swift index c291c11..c8f68c7 100644 --- a/transpiled/QOI.swift +++ b/transpiled/QOI.swift @@ -1,20 +1,5 @@ // Generated automatically with "cito". Do not edit. -/// QOI color space metadata. -/// Saved in the file header, but doesn't affect encoding or decoding in any way. -public class QOIColorspace -{ - - /// sRGBA. - public static let srgb = 0 - - /// sRGB with linear alpha. - public static let srgbLinearAlpha = 1 - - /// Linear RGBA. - public static let linear = 15 -} - /// Encoder of the "Quite OK Image" (QOI) format. /// Losslessly compresses an image to a byte array. public class QOIEncoder @@ -27,7 +12,7 @@ public class QOIEncoder fileprivate static let headerSize = 14 - fileprivate static let paddingSize = 4 + fileprivate static let paddingSize = 8 private var encoded : ArrayRef? @@ -39,7 +24,7 @@ public class QOIEncoder /// - parameter alpha Whether the image has the alpha channel (transparency). public static func canEncode(_ width : Int, _ height : Int, _ alpha : Bool) -> Bool { - return width > 0 && height > 0 && height <= 2147483629 / width / (alpha ? 5 : 4) + return width > 0 && height > 0 && height <= 2147483625 / width / (alpha ? 5 : 4) } /// Encodes the given image. @@ -48,14 +33,14 @@ public class QOIEncoder /// - parameter height Image height in pixels. /// - parameter pixels Pixels of the image, top-down, left-to-right. /// - parameter alpha `false` specifies that all pixels are opaque. High bytes of `pixels` elements are ignored then. - /// - parameter colorspace Specifies the color space. See `QOIColorspace`. - public func encode(_ width : Int, _ height : Int, _ pixels : ArrayRef?, _ alpha : Bool, _ colorspace : Int) -> Bool + /// - parameter linearColorspace Specifies the color space. + public func encode(_ width : Int, _ height : Int, _ pixels : ArrayRef?, _ alpha : Bool, _ linearColorspace : Bool) -> Bool { if pixels === nil || !QOIEncoder.canEncode(width, height, alpha) { return false } let pixelsSize : Int = width * height - let encoded : ArrayRef? = ArrayRef(repeating: 0, count: 14 + pixelsSize * (alpha ? 5 : 4) + 4) + let encoded : ArrayRef? = ArrayRef(repeating: 0, count: 14 + pixelsSize * (alpha ? 5 : 4) + 8) encoded![0] = 113 encoded![1] = 111 encoded![2] = 105 @@ -69,7 +54,7 @@ public class QOIEncoder encoded![10] = UInt8(height >> 8 & 255) encoded![11] = UInt8(height & 255) encoded![12] = alpha ? 4 : 3 - encoded![13] = UInt8(colorspace) + encoded![13] = linearColorspace ? 1 : 0 var index = [Int](repeating: 0, count: 64) var encodedOffset : Int = 14 var lastPixel : Int = -16777216 @@ -83,23 +68,19 @@ public class QOIEncoder } if pixel == lastPixel { run += 1 - } - if run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize) { - if run <= 32 { - encoded![encodedOffset] = UInt8(64 + run - 1) + if run == 62 || pixelsOffset >= pixelsSize { + encoded![encodedOffset] = UInt8(191 + run) encodedOffset += 1 + run = 0 } - else { - run -= 33 - encoded![encodedOffset] = UInt8(96 + run >> 8) - encodedOffset += 1 - encoded![encodedOffset] = UInt8(run & 255) + } + else { + if run > 0 { + encoded![encodedOffset] = UInt8(191 + run) encodedOffset += 1 + run = 0 } - run = 0 - } - if pixel != lastPixel { - let indexOffset : Int = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63 + let indexOffset : Int = (pixel >> 16 * 3 + pixel >> 8 * 5 + pixel & 63 * 7 + pixel >> 24 * 11) & 63 if pixel == index[indexOffset] { encoded![encodedOffset] = UInt8(indexOffset) encodedOffset += 1 @@ -110,59 +91,47 @@ public class QOIEncoder let g : Int = pixel >> 8 & 255 let b : Int = pixel & 255 let a : Int = pixel >> 24 & 255 - var dr : Int = r - lastPixel >> 16 & 255 - let dg : Int = g - lastPixel >> 8 & 255 - var db : Int = b - lastPixel & 255 - let da : Int = a - lastPixel >> 24 & 255 - if dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15 { - if da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1 { - encoded![encodedOffset] = UInt8(170 + dr << 4 + dg << 2 + db) - encodedOffset += 1 - } - else if da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7 { - encoded![encodedOffset] = UInt8(208 + dr) - encodedOffset += 1 - encoded![encodedOffset] = UInt8(136 + dg << 4 + db) - encodedOffset += 1 - } - else { - dr += 16 - encoded![encodedOffset] = UInt8(224 + dr >> 1) - encodedOffset += 1 - db += 16 - encoded![encodedOffset] = UInt8((dr & 1) << 7 + (dg + 16) << 2 + db >> 3) - encodedOffset += 1 - encoded![encodedOffset] = UInt8((db & 7) << 5 + da + 16) - encodedOffset += 1 - } + if (pixel ^ lastPixel) >> 24 != 0 { + encoded![encodedOffset] = 255 + encoded![encodedOffset + 1] = UInt8(r) + encoded![encodedOffset + 2] = UInt8(g) + encoded![encodedOffset + 3] = UInt8(b) + encoded![encodedOffset + 4] = UInt8(a) + encodedOffset += 5 } else { - encoded![encodedOffset] = UInt8(240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0)) - encodedOffset += 1 - if dr != 0 { - encoded![encodedOffset] = UInt8(r) - encodedOffset += 1 - } - if dg != 0 { - encoded![encodedOffset] = UInt8(g) - encodedOffset += 1 - } - if db != 0 { - encoded![encodedOffset] = UInt8(b) + var dr : Int = r - lastPixel >> 16 & 255 + let dg : Int = g - lastPixel >> 8 & 255 + var db : Int = b - lastPixel & 255 + if dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1 { + encoded![encodedOffset] = UInt8(106 + dr << 4 + dg << 2 + db) encodedOffset += 1 } - if da != 0 { - encoded![encodedOffset] = UInt8(a) - encodedOffset += 1 + else { + dr -= dg + db -= dg + if dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7 { + encoded![encodedOffset] = UInt8(160 + dg) + encoded![encodedOffset + 1] = UInt8(136 + dr << 4 + db) + encodedOffset += 2 + } + else { + encoded![encodedOffset] = 254 + encoded![encodedOffset + 1] = UInt8(r) + encoded![encodedOffset + 2] = UInt8(g) + encoded![encodedOffset + 3] = UInt8(b) + encodedOffset += 4 + } } } } lastPixel = pixel } } - encoded!.fill(0, encodedOffset, 4) + encoded!.fill(0, encodedOffset, 7) + encoded![encodedOffset + 8 - 1] = 1 self.encoded = encoded - self.encodedSize = encodedOffset + 4 + self.encodedSize = encodedOffset + 8 return true } @@ -199,7 +168,7 @@ public class QOIDecoder private var alpha : Bool = false - private var colorspace : Int = 0 + private var linearColorspace : Bool = false /// Decodes the given QOI file contents. /// Returns `true` if decoded successfully. @@ -208,7 +177,7 @@ public class QOIDecoder public func decode(_ encoded : ArrayRef?, _ ciParamEncodedSize : Int) -> Bool { var encodedSize : Int = ciParamEncodedSize - if encoded === nil || encodedSize < 19 || encoded![0] != 113 || encoded![1] != 111 || encoded![2] != 105 || encoded![3] != 102 { + if encoded === nil || encodedSize < 23 || encoded![0] != 113 || encoded![1] != 111 || encoded![2] != 105 || encoded![3] != 102 { return false } let width : Int = Int(encoded![4]) << 24 | Int(encoded![5]) << 16 | Int(encoded![6]) << 8 | Int(encoded![7]) @@ -216,9 +185,29 @@ public class QOIDecoder if width <= 0 || height <= 0 || height > 2147483647 / width { return false } + switch encoded![12] { + case 3: + self.alpha = false + break + case 4: + self.alpha = true + break + default: + return false + } + switch encoded![13] { + case 0: + self.linearColorspace = false + break + case 1: + self.linearColorspace = true + break + default: + return false + } let pixelsSize : Int = width * height let pixels : ArrayRef? = ArrayRef(repeating: 0, count: pixelsSize) - encodedSize -= 4 + encodedSize -= 8 var encodedOffset : Int = 14 var index = [Int](repeating: 0, count: 64) var pixel : Int = -16777216 @@ -229,64 +218,43 @@ public class QOIDecoder } var e : Int = Int(encoded![encodedOffset]) encodedOffset += 1 - if e < 128 { - if e < 64 { - pixel = index[e] - pixels![pixelsOffset] = pixel - pixelsOffset += 1 - } - else { - var run : Int - if e < 96 { - run = e - 63 - } - else { - run = 33 + (e - 96) << 8 + Int(encoded![encodedOffset]) - encodedOffset += 1 - } - if pixelsOffset + run > pixelsSize { + switch e >> 6 { + case 0: + pixel = index[e] + pixels![pixelsOffset] = pixel + pixelsOffset += 1 + continue + case 1: + pixel = pixel & -16777216 | (pixel + (e >> 4 - 4 - 2) << 16) & 16711680 | (pixel + (e >> 2 & 3 - 2) << 8) & 65280 | (pixel + e & 3 - 2) & 255 + break + case 2: + e -= 160 + let rb : Int = Int(encoded![encodedOffset]) + encodedOffset += 1 + pixel = pixel & -16777216 | (pixel + (e + rb >> 4 - 8) << 16) & 16711680 | (pixel + e << 8) & 65280 | (pixel + e + rb & 15 - 8) & 255 + break + default: + if e < 254 { + e -= 191 + if pixelsOffset + e > pixelsSize { return false } - pixels!.fill(pixel, pixelsOffset, run) - pixelsOffset += run + pixels!.fill(pixel, pixelsOffset, e) + pixelsOffset += e + continue } - continue - } - else if e < 224 { - if e < 192 { - pixel = pixel & -16777216 | (pixel + (e >> 4 - 8 - 2) << 16) & 16711680 | (pixel + (e >> 2 & 3 - 2) << 8) & 65280 | (pixel + e & 3 - 2) & 255 + if e == 254 { + pixel = pixel & -16777216 | Int(encoded![encodedOffset]) << 16 | Int(encoded![encodedOffset + 1]) << 8 | Int(encoded![encodedOffset + 2]) + encodedOffset += 3 } else { - let d : Int = Int(encoded![encodedOffset]) - encodedOffset += 1 - pixel = pixel & -16777216 | (pixel + (e - 192 - 16) << 16) & 16711680 | (pixel + (d >> 4 - 8) << 8) & 65280 | (pixel + d & 15 - 8) & 255 - } - } - else if e < 240 { - e = e << 16 | Int(encoded![encodedOffset]) << 8 | Int(encoded![encodedOffset + 1]) - encodedOffset += 2 - pixel = (pixel + (e & 31 - 16) << 24) & -16777216 | (pixel + (e >> 15 - 448 - 16) << 16) & 16711680 | (pixel + (e >> 10 & 31 - 16) << 8) & 65280 | (pixel + e >> 5 & 31 - 16) & 255 - } - else { - if e & 8 != 0 { - pixel = pixel & -16711681 | Int(encoded![encodedOffset]) << 16 - encodedOffset += 1 - } - if e & 4 != 0 { - pixel = pixel & -65281 | Int(encoded![encodedOffset]) << 8 - encodedOffset += 1 - } - if e & 2 != 0 { - pixel = pixel & -256 | Int(encoded![encodedOffset]) - encodedOffset += 1 - } - if e & 1 != 0 { - pixel = pixel & 16777215 | Int(encoded![encodedOffset]) << 24 - encodedOffset += 1 + pixel = Int(encoded![encodedOffset + 3]) << 24 | Int(encoded![encodedOffset]) << 16 | Int(encoded![encodedOffset + 1]) << 8 | Int(encoded![encodedOffset + 2]) + encodedOffset += 4 } + break } - index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel - pixels![pixelsOffset] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] + index[(pixel >> 16 * 3 + pixel >> 8 * 5 + pixel & 63 * 7 + pixel >> 24 * 11) & 63] = pixel + pixels![pixelsOffset] = index[(pixel >> 16 * 3 + pixel >> 8 * 5 + pixel & 63 * 7 + pixel >> 24 * 11) & 63] pixelsOffset += 1 } if encodedOffset != encodedSize { @@ -295,8 +263,6 @@ public class QOIDecoder self.width = width self.height = height self.pixels = pixels - self.alpha = encoded![12] == 4 - self.colorspace = Int(encoded![13]) return true } @@ -320,16 +286,16 @@ public class QOIDecoder } /// Returns the information about the alpha channel from the file header. - public func getAlpha() -> Bool + public func hasAlpha() -> Bool { return self.alpha } /// Returns the color space information from the file header. - /// See `QOIColorspace`. - public func getColorspace() -> Int + /// `false` = sRGB with linear alpha channel.`true` = all channels linear. + public func isLinearColorspace() -> Bool { - return self.colorspace + return self.linearColorspace } } diff --git a/transpiled/QOIDecoder.java b/transpiled/QOIDecoder.java index a3f339f..0df7c7e 100644 --- a/transpiled/QOIDecoder.java +++ b/transpiled/QOIDecoder.java @@ -17,7 +17,7 @@ public QOIDecoder() private int height; private int[] pixels; private boolean alpha; - private int colorspace; + private boolean linearColorspace; /** * Decodes the given QOI file contents. @@ -27,15 +27,35 @@ public QOIDecoder() */ public final boolean decode(byte[] encoded, int encodedSize) { - if (encoded == null || encodedSize < 19 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) + if (encoded == null || encodedSize < 23 || encoded[0] != 113 || encoded[1] != 111 || encoded[2] != 105 || encoded[3] != 102) return false; int width = (encoded[4] & 0xff) << 24 | (encoded[5] & 0xff) << 16 | (encoded[6] & 0xff) << 8 | encoded[7] & 0xff; int height = (encoded[8] & 0xff) << 24 | (encoded[9] & 0xff) << 16 | (encoded[10] & 0xff) << 8 | encoded[11] & 0xff; if (width <= 0 || height <= 0 || height > 2147483647 / width) return false; + switch (encoded[12] & 0xff) { + case 3: + this.alpha = false; + break; + case 4: + this.alpha = true; + break; + default: + return false; + } + switch (encoded[13] & 0xff) { + case 0: + this.linearColorspace = false; + break; + case 1: + this.linearColorspace = true; + break; + default: + return false; + } int pixelsSize = width * height; int[] pixels = new int[pixelsSize]; - encodedSize -= 4; + encodedSize -= 8; int encodedOffset = 14; final int[] index = new int[64]; int pixel = -16777216; @@ -43,54 +63,44 @@ public final boolean decode(byte[] encoded, int encodedSize) if (encodedOffset >= encodedSize) return false; int e = encoded[encodedOffset++] & 0xff; - if (e < 128) { - if (e < 64) - pixels[pixelsOffset++] = pixel = index[e]; - else { - int run; - if (e < 96) - run = e - 63; - else - run = 33 + ((e - 96) << 8) + (encoded[encodedOffset++] & 0xff); - if (pixelsOffset + run > pixelsSize) + switch (e >> 6) { + case 0: + pixels[pixelsOffset++] = pixel = index[e]; + continue; + case 1: + pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 4 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); + break; + case 2: + e -= 160; + int rb = encoded[encodedOffset++] & 0xff; + pixel = (pixel & -16777216) | ((pixel + ((e + (rb >> 4) - 8) << 16)) & 16711680) | ((pixel + (e << 8)) & 65280) | ((pixel + e + (rb & 15) - 8) & 255); + break; + default: + if (e < 254) { + e -= 191; + if (pixelsOffset + e > pixelsSize) return false; - Arrays.fill(pixels, pixelsOffset, pixelsOffset + run, pixel); - pixelsOffset += run; + Arrays.fill(pixels, pixelsOffset, pixelsOffset + e, pixel); + pixelsOffset += e; + continue; + } + if (e == 254) { + pixel = (pixel & -16777216) | (encoded[encodedOffset] & 0xff) << 16 | (encoded[encodedOffset + 1] & 0xff) << 8 | encoded[encodedOffset + 2] & 0xff; + encodedOffset += 3; } - continue; - } - else if (e < 224) { - if (e < 192) - pixel = (pixel & -16777216) | ((pixel + (((e >> 4) - 8 - 2) << 16)) & 16711680) | ((pixel + (((e >> 2 & 3) - 2) << 8)) & 65280) | ((pixel + (e & 3) - 2) & 255); else { - int d = encoded[encodedOffset++] & 0xff; - pixel = (pixel & -16777216) | ((pixel + ((e - 192 - 16) << 16)) & 16711680) | ((pixel + (((d >> 4) - 8) << 8)) & 65280) | ((pixel + (d & 15) - 8) & 255); + pixel = (encoded[encodedOffset + 3] & 0xff) << 24 | (encoded[encodedOffset] & 0xff) << 16 | (encoded[encodedOffset + 1] & 0xff) << 8 | encoded[encodedOffset + 2] & 0xff; + encodedOffset += 4; } + break; } - else if (e < 240) { - e = e << 16 | (encoded[encodedOffset] & 0xff) << 8 | encoded[encodedOffset + 1] & 0xff; - encodedOffset += 2; - pixel = ((pixel + (((e & 31) - 16) << 24)) & -16777216) | ((pixel + (((e >> 15) - 448 - 16) << 16)) & 16711680) | ((pixel + (((e >> 10 & 31) - 16) << 8)) & 65280) | ((pixel + (e >> 5 & 31) - 16) & 255); - } - else { - if ((e & 8) != 0) - pixel = (pixel & -16711681) | (encoded[encodedOffset++] & 0xff) << 16; - if ((e & 4) != 0) - pixel = (pixel & -65281) | (encoded[encodedOffset++] & 0xff) << 8; - if ((e & 2) != 0) - pixel = (pixel & -256) | encoded[encodedOffset++] & 0xff; - if ((e & 1) != 0) - pixel = (pixel & 16777215) | (encoded[encodedOffset++] & 0xff) << 24; - } - pixels[pixelsOffset++] = index[(pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63] = pixel; + pixels[pixelsOffset++] = index[((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63] = pixel; } if (encodedOffset != encodedSize) return false; this.width = width; this.height = height; this.pixels = pixels; - this.alpha = encoded[12] == 4; - this.colorspace = encoded[13] & 0xff; return true; } @@ -122,17 +132,17 @@ public final int[] getPixels() /** * Returns the information about the alpha channel from the file header. */ - public final boolean getAlpha() + public final boolean hasAlpha() { return this.alpha; } /** * Returns the color space information from the file header. - * See QOIColorspace. + * false = sRGB with linear alpha channel.true = all channels linear. */ - public final int getColorspace() + public final boolean isLinearColorspace() { - return this.colorspace; + return this.linearColorspace; } } diff --git a/transpiled/QOIEncoder.java b/transpiled/QOIEncoder.java index 53fb1d8..4bd3d8e 100644 --- a/transpiled/QOIEncoder.java +++ b/transpiled/QOIEncoder.java @@ -17,7 +17,7 @@ public QOIEncoder() static final int HEADER_SIZE = 14; - static final int PADDING_SIZE = 4; + static final int PADDING_SIZE = 8; private byte[] encoded; private int encodedSize; @@ -29,7 +29,7 @@ public QOIEncoder() */ public static boolean canEncode(int width, int height, boolean alpha) { - return width > 0 && height > 0 && height <= 2147483629 / width / (alpha ? 5 : 4); + return width > 0 && height > 0 && height <= 2147483625 / width / (alpha ? 5 : 4); } /** @@ -39,14 +39,14 @@ public static boolean canEncode(int width, int height, boolean alpha) * @param height Image height in pixels. * @param pixels Pixels of the image, top-down, left-to-right. * @param alpha false specifies that all pixels are opaque. High bytes of pixels elements are ignored then. - * @param colorspace Specifies the color space. See QOIColorspace. + * @param linearColorspace Specifies the color space. */ - public final boolean encode(int width, int height, int[] pixels, boolean alpha, int colorspace) + public final boolean encode(int width, int height, int[] pixels, boolean alpha, boolean linearColorspace) { if (pixels == null || !canEncode(width, height, alpha)) return false; int pixelsSize = width * height; - byte[] encoded = new byte[14 + pixelsSize * (alpha ? 5 : 4) + 4]; + byte[] encoded = new byte[14 + pixelsSize * (alpha ? 5 : 4) + 8]; encoded[0] = 113; encoded[1] = 111; encoded[2] = 105; @@ -60,7 +60,7 @@ public final boolean encode(int width, int height, int[] pixels, boolean alpha, encoded[10] = (byte) (height >> 8); encoded[11] = (byte) height; encoded[12] = (byte) (alpha ? 4 : 3); - encoded[13] = (byte) colorspace; + encoded[13] = (byte) (linearColorspace ? 1 : 0); final int[] index = new int[64]; int encodedOffset = 14; int lastPixel = -16777216; @@ -69,20 +69,18 @@ public final boolean encode(int width, int height, int[] pixels, boolean alpha, int pixel = pixels[pixelsOffset++]; if (!alpha) pixel |= -16777216; - if (pixel == lastPixel) - run++; - if (run > 0 && (pixel != lastPixel || run == 8224 || pixelsOffset >= pixelsSize)) { - if (run <= 32) - encoded[encodedOffset++] = (byte) (64 + run - 1); - else { - run -= 33; - encoded[encodedOffset++] = (byte) (96 + (run >> 8)); - encoded[encodedOffset++] = (byte) run; + if (pixel == lastPixel) { + if (++run == 62 || pixelsOffset >= pixelsSize) { + encoded[encodedOffset++] = (byte) (191 + run); + run = 0; } - run = 0; } - if (pixel != lastPixel) { - int indexOffset = (pixel >> 24 ^ pixel >> 16 ^ pixel >> 8 ^ pixel) & 63; + else { + if (run > 0) { + encoded[encodedOffset++] = (byte) (191 + run); + run = 0; + } + int indexOffset = ((pixel >> 16) * 3 + (pixel >> 8) * 5 + (pixel & 63) * 7 + (pixel >> 24) * 11) & 63; if (pixel == index[indexOffset]) encoded[encodedOffset++] = (byte) indexOffset; else { @@ -91,43 +89,45 @@ public final boolean encode(int width, int height, int[] pixels, boolean alpha, int g = pixel >> 8 & 255; int b = pixel & 255; int a = pixel >> 24 & 255; - int dr = r - (lastPixel >> 16 & 255); - int dg = g - (lastPixel >> 8 & 255); - int db = b - (lastPixel & 255); - int da = a - (lastPixel >> 24 & 255); - if (dr >= -16 && dr <= 15 && dg >= -16 && dg <= 15 && db >= -16 && db <= 15 && da >= -16 && da <= 15) { - if (da == 0 && dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) - encoded[encodedOffset++] = (byte) (170 + (dr << 4) + (dg << 2) + db); - else if (da == 0 && dg >= -8 && dg <= 7 && db >= -8 && db <= 7) { - encoded[encodedOffset++] = (byte) (208 + dr); - encoded[encodedOffset++] = (byte) (136 + (dg << 4) + db); - } - else { - dr += 16; - encoded[encodedOffset++] = (byte) (224 + (dr >> 1)); - db += 16; - encoded[encodedOffset++] = (byte) (((dr & 1) << 7) + ((dg + 16) << 2) + (db >> 3)); - encoded[encodedOffset++] = (byte) (((db & 7) << 5) + da + 16); - } + if ((pixel ^ lastPixel) >> 24 != 0) { + encoded[encodedOffset] = (byte) 255; + encoded[encodedOffset + 1] = (byte) r; + encoded[encodedOffset + 2] = (byte) g; + encoded[encodedOffset + 3] = (byte) b; + encoded[encodedOffset + 4] = (byte) a; + encodedOffset += 5; } else { - encoded[encodedOffset++] = (byte) (240 | (dr != 0 ? 8 : 0) | (dg != 0 ? 4 : 0) | (db != 0 ? 2 : 0) | (da != 0 ? 1 : 0)); - if (dr != 0) - encoded[encodedOffset++] = (byte) r; - if (dg != 0) - encoded[encodedOffset++] = (byte) g; - if (db != 0) - encoded[encodedOffset++] = (byte) b; - if (da != 0) - encoded[encodedOffset++] = (byte) a; + int dr = r - (lastPixel >> 16 & 255); + int dg = g - (lastPixel >> 8 & 255); + int db = b - (lastPixel & 255); + if (dr >= -2 && dr <= 1 && dg >= -2 && dg <= 1 && db >= -2 && db <= 1) + encoded[encodedOffset++] = (byte) (106 + (dr << 4) + (dg << 2) + db); + else { + dr -= dg; + db -= dg; + if (dr >= -8 && dr <= 7 && dg >= -32 && dg <= 31 && db >= -8 && db <= 7) { + encoded[encodedOffset] = (byte) (160 + dg); + encoded[encodedOffset + 1] = (byte) (136 + (dr << 4) + db); + encodedOffset += 2; + } + else { + encoded[encodedOffset] = (byte) 254; + encoded[encodedOffset + 1] = (byte) r; + encoded[encodedOffset + 2] = (byte) g; + encoded[encodedOffset + 3] = (byte) b; + encodedOffset += 4; + } + } } } lastPixel = pixel; } } - Arrays.fill(encoded, encodedOffset, encodedOffset + 4, (byte) 0); + Arrays.fill(encoded, encodedOffset, encodedOffset + 7, (byte) 0); + encoded[encodedOffset + 8 - 1] = 1; this.encoded = encoded; - this.encodedSize = encodedOffset + 4; + this.encodedSize = encodedOffset + 8; return true; } diff --git a/win32/win32.mk b/win32/win32.mk index ed79b7e..2c72317 100644 --- a/win32/win32.mk +++ b/win32/win32.mk @@ -1,4 +1,4 @@ -VERSION = 0.3.0 +VERSION = 1.0.0 DO_SIGN = signtool sign -d "Quite OK Image plugins $(VERSION)" -n "Open Source Developer, Piotr Fusik" -tr http://time.certum.pl -fd sha256 -td sha256 $^ && touch $@