diff options
author | Emery Hemingway <emery@dmz.rs> | 2024-09-02T23·28+0300 |
---|---|---|
committer | emery <emery@dmz.rs> | 2024-09-03T19·44+0000 |
commit | e4714db2d5f2b38b60f1ec8a200d0eb3bea0d79f (patch) | |
tree | 7bfdd1e19297c7d54ecf6804895cae87c4cf6875 | |
parent | c60f4ccfda1b65bf8152b399e0087db87fa52b3d (diff) |
feat(tools/eaglemode/plugins): QOI image plugin r/8646
https://qoiformat.org/ Change-Id: I0c11095c1ac0e65075d032f7c29649cbba9f213f Reviewed-on: https://cl.tvl.fyi/c/depot/+/12427 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
-rw-r--r-- | tools/eaglemode/plugins/qoi/default.nix | 12 | ||||
-rw-r--r-- | tools/eaglemode/plugins/qoi/etc/emCore/FpPlugins/PlQoi.emFpPlugin | 6 | ||||
-rw-r--r-- | tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm | 47 | ||||
-rw-r--r-- | tools/eaglemode/plugins/qoi/src/PlQoi.cpp | 273 | ||||
-rw-r--r-- | users/emery/eaglemode/default.nix | 9 |
5 files changed, 343 insertions, 4 deletions
diff --git a/tools/eaglemode/plugins/qoi/default.nix b/tools/eaglemode/plugins/qoi/default.nix new file mode 100644 index 000000000000..8764ac39e88d --- /dev/null +++ b/tools/eaglemode/plugins/qoi/default.nix @@ -0,0 +1,12 @@ +{ depot, pkgs, ... }: + +let + em = depot.tools.eaglemode; + emSrc = pkgs.srcOnly pkgs.em; +in +em.buildPlugin { + name = "qoi"; + version = "canon"; + src = ./.; + target = "PlQoi"; +} diff --git a/tools/eaglemode/plugins/qoi/etc/emCore/FpPlugins/PlQoi.emFpPlugin b/tools/eaglemode/plugins/qoi/etc/emCore/FpPlugins/PlQoi.emFpPlugin new file mode 100644 index 000000000000..e27f37261d77 --- /dev/null +++ b/tools/eaglemode/plugins/qoi/etc/emCore/FpPlugins/PlQoi.emFpPlugin @@ -0,0 +1,6 @@ +#%rec:emFpPlugin%# + +FileTypes = { ".qoi" } +Priority = 1.0 +Library = "PlQoi" +Function = "PlQoiFpPluginFunc" diff --git a/tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm b/tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm new file mode 100644 index 000000000000..c68b9bc6324b --- /dev/null +++ b/tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm @@ -0,0 +1,47 @@ +package PlQoi; + +use strict; +use warnings; + +sub GetDependencies +{ + return ('emCore'); +} + +sub IsEssential +{ + return 0; +} + +sub GetFileHandlingrules +{ + return (); +} + +sub GetExtraBuildOptions +{ + return (); +} + +sub Build +{ + shift; + my %options=@_; + + system( + @{$options{'unicc_call'}}, + "--math", + "--rtti", + "--exceptions", + "--bin-dir" , "bin", + "--lib-dir" , "lib", + "--obj-dir" , "obj", + "--inc-search-dir", "include", + "--link" , "emCore", + "--type" , "dynlib", + "--name" , "PlQoi", + "src/PlQoi.cpp" + )==0 or return 0; + + return 1; +} diff --git a/tools/eaglemode/plugins/qoi/src/PlQoi.cpp b/tools/eaglemode/plugins/qoi/src/PlQoi.cpp new file mode 100644 index 000000000000..1455712eff3a --- /dev/null +++ b/tools/eaglemode/plugins/qoi/src/PlQoi.cpp @@ -0,0 +1,273 @@ +#include <emCore/emFpPlugin.h> +#include <emCore/emImageFile.h> + +/* +QOI Utilities + +Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT +*/ + +#define QOI_OP_INDEX 0x00 /* 00xxxxxx */ +#define QOI_OP_DIFF 0x40 /* 01xxxxxx */ +#define QOI_OP_LUMA 0x80 /* 10xxxxxx */ +#define QOI_OP_RUN 0xc0 /* 11xxxxxx */ +#define QOI_OP_RGB 0xfe /* 11111110 */ +#define QOI_OP_RGBA 0xff /* 11111111 */ + +#define QOI_MASK_2 0xc0 /* 11000000 */ + +#define QOI_COLOR_HASH(C) (C.GetRed()*3 + C.GetGreen()*5 + C.GetBlue()*7 + C.GetAlpha()*11) + +#define QOI_MAGIC \ + (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \ + ((unsigned int)'i') << 8 | ((unsigned int)'f')) + +#define QOI_HEADER_SIZE 14 + +static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { + unsigned int a = bytes[(*p)++]; + unsigned int b = bytes[(*p)++]; + unsigned int c = bytes[(*p)++]; + unsigned int d = bytes[(*p)++]; + return a << 24 | b << 16 | c << 8 | d; +} + + +class PlQoiImageFileModel : public emImageFileModel +{ +public: + + static emRef<PlQoiImageFileModel> Acquire( + emContext & context, const emString & name, bool common=true + ); + +protected: + PlQoiImageFileModel(emContext & context, const emString & name); + virtual ~PlQoiImageFileModel(); + virtual void TryStartLoading(); + virtual bool TryContinueLoading(); + virtual void QuitLoading(); + virtual void TryStartSaving(); + virtual bool TryContinueSaving(); + virtual void QuitSaving(); + virtual emUInt64 CalcMemoryNeed(); + virtual double CalcFileProgress(); + +private: + struct LoadingState; + LoadingState * L = NULL; +}; + + +struct PlQoiImageFileModel::LoadingState { + FILE * file; + unsigned int width, height, channels; + size_t file_len; +}; + + +emRef<PlQoiImageFileModel> PlQoiImageFileModel::Acquire( + emContext & context, const emString & name, bool common +) +{ + EM_IMPL_ACQUIRE(PlQoiImageFileModel, context, name, common) +} + + +PlQoiImageFileModel::PlQoiImageFileModel( + emContext & context, const emString & name +) + : emImageFileModel(context, name) +{ +} + + +PlQoiImageFileModel::~PlQoiImageFileModel() +{ + PlQoiImageFileModel::QuitLoading(); + PlQoiImageFileModel::QuitSaving(); +} + + +void PlQoiImageFileModel::TryStartLoading() +{ + unsigned char header[QOI_HEADER_SIZE]; + unsigned int header_magic, colorspace; + int pos = 0; + + L = new LoadingState; + memset(L, 0, sizeof(LoadingState)); + L->file = fopen(GetFilePath(),"rb"); + if (!L->file) throw emException("%s",emGetErrorText(errno).Get()); + + if (fread(header, 1, sizeof(header), L->file) != sizeof(header)) { + if (ferror(L->file)) { + throw emException("%s",emGetErrorText(errno).Get()); + } + else { + throw emException("QOI header not found"); + } + } + + header_magic = qoi_read_32(header, &pos); + L->width = qoi_read_32(header, &pos); + L->height = qoi_read_32(header, &pos); + L->channels = header[pos++]; + colorspace = header[pos++]; + + if ( + L->width == 0 || L->height == 0 || + L->channels < 3 || L->channels > 4 || + colorspace > 1 || + header_magic != QOI_MAGIC + ) { + throw emException("QOI header not valid"); + } + + fseek(L->file, 0, SEEK_END); + L->file_len = ftell(L->file); + + if (L->file_len <= QOI_HEADER_SIZE || fseek(L->file, 0, SEEK_SET) != 0) { + throw emException("QOI data incomplete"); + } + + FileFormatInfo = "QOI "; + FileFormatInfo += ( + colorspace ? "all channels linear" : "sRGB with linear alpha" + ); + + Signal(ChangeSignal); +} + + +bool PlQoiImageFileModel::TryContinueLoading() +{ + emArray<unsigned char> data; + emColor index[64]; + emColor px { 0, 0, 0, 255 }; + int pos = QOI_HEADER_SIZE; + int run = 0; + + if (!Image.GetHeight()) { + Image.Setup(L->width, L->height, L->channels); + } + + data.SetCount(L->file_len); + if (fread(data.GetWritable(), 1, L->file_len, L->file) < L->file_len) { + if (ferror(L->file)) { + throw emException("%s",emGetErrorText(errno).Get()); + } + else { + throw emException("QOI data incomplete"); + } + } + + memset(index, 0, sizeof(index)); + + for (int px_y = 0; px_y < L->height; px_y++) { + for (int px_x = 0; px_x < L->width; px_x++) { + if (run > 0) { + run--; + } else if (pos < data.GetCount()) { + int b1 = data.Get(pos++); + + if (b1 == QOI_OP_RGB) { + px.SetRed( data.Get(pos++)); + px.SetGreen( data.Get(pos++)); + px.SetBlue( data.Get(pos++)); + } else if (b1 == QOI_OP_RGBA) { + px.SetRed( data.Get(pos++)); + px.SetGreen( data.Get(pos++)); + px.SetBlue( data.Get(pos++)); + px.SetAlpha( data.Get(pos++)); + } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { + px = index[b1]; + } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { + px.SetRed( + px.GetRed() + ((b1 >> 4) & 0x03) - 2); + px.SetGreen( + px.GetGreen() + ((b1 >> 2) & 0x03) - 2); + px.SetBlue( + px.GetBlue() + ( b1 & 0x03) - 2); + } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { + int b2 = data.Get(pos++); + int vg = (b1 & 0x3f) - 32; + px.SetRed( + px.GetRed() + vg - 8 + ((b2 >> 4) & 0x0f)); + px.SetGreen( + px.GetGreen() + vg); + px.SetBlue( + px.GetBlue() + vg - 8 + (b2 & 0x0f)); + } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { + run = (b1 & 0x3f); + } + index[QOI_COLOR_HASH(px) % 64] = px; + } + Image.SetPixel(px_x, px_y, px); + } + } + + Signal(ChangeSignal); + return true; +} + + +void PlQoiImageFileModel::QuitLoading() +{ + if (L) { + if (L->file) fclose(L->file); + delete L; + L = NULL; + } +} + + +void PlQoiImageFileModel::TryStartSaving() +{ + throw emException("PlQoiImageFileModel: Saving not implemented."); +} + + +bool PlQoiImageFileModel::TryContinueSaving() +{ + return false; +} + + +void PlQoiImageFileModel::QuitSaving() +{ +} + + +emUInt64 PlQoiImageFileModel::CalcMemoryNeed() +{ + return + (emUInt64)L->width * L->height * L->channels + L->file_len; +} + + +double PlQoiImageFileModel::CalcFileProgress() +{ + return 0.0; +} + +extern "C" { + emPanel * PlQoiFpPluginFunc( + emPanel::ParentArg parent, const emString & name, + const emString & path, emFpPlugin * plugin, + emString * errorBuf + ) + { + if (plugin->Properties.GetCount()) { + *errorBuf="PlQoiFpPlugin: No properties allowed."; + return NULL; + } + return new emImageFilePanel( + parent, name, + PlQoiImageFileModel::Acquire( + parent.GetRootContext(), path + ) + ); + } +} diff --git a/users/emery/eaglemode/default.nix b/users/emery/eaglemode/default.nix index c4b51a4e7c0c..89b4f60ec174 100644 --- a/users/emery/eaglemode/default.nix +++ b/users/emery/eaglemode/default.nix @@ -3,9 +3,10 @@ let config = depot.tools.eaglemode.etcDir { - extraPaths = [ depot.tools.eaglemode.commands.B ]; + extraPaths = [ + depot.tools.eaglemode.commands.B + depot.tools.eaglemode.plugins.qoi + ]; }; in -depot.tools.eaglemode.withConfig { - inherit config; -} +depot.tools.eaglemode.withConfig { inherit config; } |