feat(tools/eaglemode/plugins): QOI image plugin
https://qoiformat.org/ Change-Id: I0c11095c1ac0e65075d032f7c29649cbba9f213f Reviewed-on: https://cl.tvl.fyi/c/depot/+/12427 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
This commit is contained in:
parent
c60f4ccfda
commit
e4714db2d5
5 changed files with 343 additions and 4 deletions
12
tools/eaglemode/plugins/qoi/default.nix
Normal file
12
tools/eaglemode/plugins/qoi/default.nix
Normal file
|
@ -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";
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
#%rec:emFpPlugin%#
|
||||||
|
|
||||||
|
FileTypes = { ".qoi" }
|
||||||
|
Priority = 1.0
|
||||||
|
Library = "PlQoi"
|
||||||
|
Function = "PlQoiFpPluginFunc"
|
47
tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm
Normal file
47
tools/eaglemode/plugins/qoi/makers/PlQoi.maker.pm
Normal file
|
@ -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;
|
||||||
|
}
|
273
tools/eaglemode/plugins/qoi/src/PlQoi.cpp
Normal file
273
tools/eaglemode/plugins/qoi/src/PlQoi.cpp
Normal file
|
@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,10 @@
|
||||||
|
|
||||||
let
|
let
|
||||||
config = depot.tools.eaglemode.etcDir {
|
config = depot.tools.eaglemode.etcDir {
|
||||||
extraPaths = [ depot.tools.eaglemode.commands.B ];
|
extraPaths = [
|
||||||
|
depot.tools.eaglemode.commands.B
|
||||||
|
depot.tools.eaglemode.plugins.qoi
|
||||||
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
depot.tools.eaglemode.withConfig {
|
depot.tools.eaglemode.withConfig { inherit config; }
|
||||||
inherit config;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue