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
|
||||
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; }
|
||||
|
|
Loading…
Reference in a new issue