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:
Emery Hemingway 2024-09-03 02:28:31 +03:00 committed by emery
parent c60f4ccfda
commit e4714db2d5
5 changed files with 343 additions and 4 deletions

View 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";
}

View file

@ -0,0 +1,6 @@
#%rec:emFpPlugin%#
FileTypes = { ".qoi" }
Priority = 1.0
Library = "PlQoi"
Function = "PlQoiFpPluginFunc"

View 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;
}

View 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
)
);
}
}

View file

@ -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; }