This commit is contained in:
寂静的羽夏 2022-11-12 21:33:48 +08:00
parent b50ee48459
commit cfaec7e217
49 changed files with 2464 additions and 1544 deletions

View File

@ -1,75 +1,17 @@
#include "settings.h"
#include <DMessageManager>
#include <DSettingsOption>
#include <QApplication>
#include <QSettings>
#include <QStandardPaths>
#include <QStyleFactory>
Settings *Settings::s_pSetting = nullptr;
Settings::Settings(QObject *parent) : QObject(parent) {
QString strConfigPath =
QString("%1/%2/%3/config.conf")
.arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation))
.arg(qApp->organizationName())
.arg(qApp->applicationName());
Settings::Settings(QObject *parent) : QObject(parent) {}
m_backend = new QSettingBackend(strConfigPath);
settings = DSettings::fromJsonFile(":/settings.json");
settings->setBackend(m_backend);
#define BindConfigSignal(Var, SettingName, Signal) \
auto Var = settings->option(SettingName); \
connect(Var, &Dtk::Core::DSettingsOption::valueChanged, \
this, [=](QVariant value) Signal);
BindConfigSignal(quality, "", { emit sigAdjustQuality(value.toInt()); })
auto windowState = settings->option("appearance.window.windowsize");
QMap<QString, QVariant> windowStateMap;
windowStateMap.insert("keys", QStringList() << "window_normal"
<< "window_maximum"
<< "window_minimum"
<< "fullscreen");
windowStateMap.insert("values", QStringList()
<< tr("Normal") << tr("Maximum")
<< tr("Minimum") << tr("Fullscreen"));
windowState->setData("items", windowStateMap);
}
Settings::~Settings() {}
void Settings::setSettingDialog(DSettingsDialog *settingsDialog) {
m_pSettingsDialog = settingsDialog;
}
void Settings::applySetting() {
#define Apply(Var, SettingName, Signal) \
auto Var = settings->option(SettingName); \
if (Var != nullptr) \
emit Signal;
Apply(quality, "editor.image.quality",
sigAdjustQuality(quality->value().toInt()));
Apply(windowstate, "appearance.window.windowsize",
sigChangeWindowState(windowstate->value().toString()));
}
// This function is workaround, it will remove after DTK fixed SettingDialog
// theme bug.
void Settings::dtkThemeWorkaround(QWidget *parent, const QString &theme) {
parent->setStyle(QStyleFactory::create(theme));
for (auto obj : parent->children()) {
auto w = qobject_cast<QWidget *>(obj);
if (!w) {
continue;
}
dtkThemeWorkaround(w, theme);
void Settings::saveWindowStatus(DMainWindow *wnd) {
if (wnd != nullptr) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("geometry", wnd->saveGeometry());
settings.setValue("windowState", wnd->saveState());
}
}
@ -80,32 +22,7 @@ Settings *Settings::instance() {
return s_pSetting;
}
DDialog *Settings::createDialog(const QString &title, const QString &content,
const bool &bIsConflicts) {
DDialog *dialog = new DDialog(title, content, m_pSettingsDialog);
dialog->setWindowFlags(dialog->windowFlags() | Qt::WindowStaysOnBottomHint);
dialog->setIcon(QIcon(":/images/setting.png"));
if (bIsConflicts) {
dialog->addButton(QString(tr("Cancel")), true, DDialog::ButtonNormal);
dialog->addButton(QString(tr("Replace")), false, DDialog::ButtonRecommend);
} else {
dialog->addButton(QString(tr("OK")), true, DDialog::ButtonRecommend);
}
return dialog;
}
void Settings::saveWindowState(DMainWindow *wnd) {
if (wnd != nullptr) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("geometry", wnd->saveGeometry());
settings.setValue("windowState", wnd->saveState());
}
}
void Settings::loadWindowState(DMainWindow *wnd) {
void Settings::loadWindowStatus(DMainWindow *wnd) {
if (wnd != nullptr) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
@ -114,12 +31,48 @@ void Settings::loadWindowState(DMainWindow *wnd) {
}
}
void Settings::setWindowState(int state) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("state", state);
}
int Settings::windowState() {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
return settings.value("state", 0).toInt();
}
void Settings::saveFileDialogCurrent(QString path) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("curpath", path);
}
void Settings::setTerminal(QString path) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("terminal", path);
}
QString Settings::terminal() {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
return settings.value("terminal", "/bin/bash").toString().trimmed();
}
void Settings::setReprocessingCmdline(QString cmdline) {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
settings.setValue("cmdline", cmdline);
}
QString Settings::reprocessingCmdline() {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());
return settings.value("cmdline").toString().trimmed();
}
QString Settings::loadFileDialogCurrent() {
QSettings settings(QApplication::organizationName(),
QApplication::applicationName());

View File

@ -1,48 +1,39 @@
#ifndef SETTINGS_H
#define SETTINGS_H
#include "GifImage/encoder/GifEncoder.h"
#include <DDialog>
#include <DMainWindow>
#include <DSettingsDialog>
#include <QObject>
#include <qsettingbackend.h>
DWIDGET_USE_NAMESPACE
DCORE_USE_NAMESPACE
DTK_USE_NAMESPACE
class Settings : public QObject {
Q_OBJECT
public:
explicit Settings(QObject *parent = nullptr);
~Settings();
void dtkThemeWorkaround(QWidget *parent, const QString &theme);
static Settings *instance();
void setSettingDialog(DSettingsDialog *settingsDialog);
void applySetting();
void saveWindowState(DMainWindow *wnd);
void loadWindowState(DMainWindow *wnd);
void saveWindowStatus(DMainWindow *wnd);
void loadWindowStatus(DMainWindow *wnd);
void setWindowState(int state);
int windowState();
QString loadFileDialogCurrent();
void saveFileDialogCurrent(QString path);
DSettings *settings;
void setTerminal(QString path);
QString terminal();
void setReprocessingCmdline(QString cmdline);
QString reprocessingCmdline();
signals:
void sigAdjustQuality(int quality);
void sigChangeWindowState(QString state);
void sigreprocessingCmdlineChanged();
void sigFileDialogCurrentChanged();
private:
DDialog *createDialog(const QString &title, const QString &content,
const bool &bIsConflicts);
private:
Dtk::Core::QSettingBackend *m_backend;
DSettingsDialog *m_pSettingsDialog;
static Settings *s_pSetting;
DDialog *m_pDialog;
};
#endif // SETTINGS_H

View File

@ -85,3 +85,8 @@ void CropGifDialog::setSelRect(QRectF rect) {
m_h->setValue(h);
emit selRectChanged(x, y, w, h);
}
void CropGifDialog::closeEvent(QCloseEvent *event) {
Q_UNUSED(event);
emit crop(-1, -1, -1, -1);
}

View File

@ -25,6 +25,9 @@ signals:
void selRectChanged(int x, int y, int w, int h);
void pressCancel();
protected:
void closeEvent(QCloseEvent *event) override;
private:
DButtonBox *btnbox;
DSpinBox *m_x, *m_y, *m_w, *m_h;

View File

@ -34,6 +34,7 @@
#include <QListWidgetItem>
#include <QMessageBox>
#include <QMimeData>
#include <QProcess>
#include <QRubberBand>
#include <QSettings>
#include <QShortcut>
@ -584,6 +585,8 @@ MainWindow::MainWindow(DMainWindow *parent) : DMainWindow(parent) {
CheckEnabled;
editor->endCrop();
this->setEditMode(true);
if (x < 0 || y < 0 || w < 0 || h < 0)
return;
undo.push(new CropImageCommand(&gif, x, y, w, h));
editor->fitPicEditor();
});
@ -632,27 +635,22 @@ MainWindow::MainWindow(DMainWindow *parent) : DMainWindow(parent) {
ConnectShortCut(keycutpic, MainWindow::on_cutpic);
ConnectShortCut(keyscaledelay, MainWindow::on_scaledelay);
m_settings = new Settings(this);
connect(m_settings, &Settings::sigAdjustQuality, this,
[=](int v) { _quality = v; });
connect(m_settings, &Settings::sigChangeWindowState,
[=](QString mode) { _windowmode = mode; });
m_settings->applySetting();
if (_windowmode == "window_normal") {
setWindowState(Qt::WindowState::WindowActive);
} else if (_windowmode == "window_maximum") {
setWindowState(Qt::WindowState::WindowMaximized);
} else if (_windowmode == "window_minimum") {
setWindowState(Qt::WindowState::WindowMinimized);
} else {
setWindowState(Qt::WindowState::WindowFullScreen);
}
m_settings->loadWindowState(this);
m_settings = Settings::instance();
m_settings->loadWindowStatus(this);
lastusedpath = m_settings->loadFileDialogCurrent();
m_settingd.state = WinState(m_settings->windowState());
switch (m_settingd.state) {
case Normal:
break;
case Maximum:
showMaximized();
break;
case Minimum:
showMinimized();
break;
}
m_settingd.cmdline = m_settings->reprocessingCmdline();
m_settingd.terminal = m_settings->terminal();
}
void MainWindow::openGif(QString filename) {
@ -746,82 +744,15 @@ void MainWindow::showGifMessage(QString message) {
}
bool MainWindow::saveGif(QString filename) {
GifEncoder gifsaver;
auto size = gif.size();
if (gifsaver.open(filename.toStdString(), uint16_t(size.width()),
uint16_t(size.height()), _quality, false, 0)) {
if (gifsaver.open(filename, size.width(), size.height())) {
QApplication::processEvents();
auto frames = gif.frames();
auto pframe = frames.begin();
auto eframe = frames.end();
auto img = pframe->image;
QApplication::processEvents();
gifsaver.push(GifEncoder::PIXEL_FORMAT_RGBA, img.constBits(), 0, 0,
img.width(), img.height(), pframe->delayTime / 10);
auto lframe = pframe;
pframe++;
for (; pframe != eframe; pframe++, lframe++) {
img = pframe->image;
auto limg = lframe->image;
auto bpl = img.bytesPerLine();
auto ls = img.height();
int x, y, x0, y0;
for (y = 0; y < ls - 1; y++) {
auto o = limg.constScanLine(y);
auto d = img.constScanLine(y);
if (memcmp(o, d, size_t(bpl))) {
break;
}
QApplication::processEvents();
}
for (y0 = ls - 1; y0 > y; y0--) {
auto o = limg.constScanLine(y0);
auto d = img.constScanLine(y0);
if (memcmp(o, d, size_t(bpl))) {
break;
}
QApplication::processEvents();
}
QTransform trans;
trans.rotate(90);
auto rimg = img.transformed(trans);
auto rlimg = limg.transformed(trans);
bpl = rimg.bytesPerLine();
ls = rimg.height();
for (x = 0; x < ls - 1; x++) {
auto o = rlimg.constScanLine(x);
auto d = rimg.constScanLine(x);
if (memcmp(o, d, size_t(bpl))) {
break;
}
QApplication::processEvents();
}
for (x0 = ls - 1; x0 > x; x0--) {
auto o = rlimg.constScanLine(x0);
auto d = rimg.constScanLine(x0);
if (memcmp(o, d, size_t(bpl))) {
break;
}
QApplication::processEvents();
}
auto timg = img.copy(adjustImageSize(x, y, x0 - x + 1, y0 - y + 1));
auto &frames = gif.frames();
for (auto &frame : frames) {
gifsaver.push(frame.image, frame.delayTime / 10);
QApplication::processEvents();
gifsaver.push(GifEncoder::PIXEL_FORMAT_RGBA, timg.constBits(), x, y,
timg.width(), timg.height(), pframe->delayTime / 10);
}
gifsaver.close();
undo.setClean();
return true;
@ -829,26 +760,6 @@ bool MainWindow::saveGif(QString filename) {
return false;
}
QRect MainWindow::adjustImageSize(int x, int y, int w, int h) {
// 由于保存图像使用神经网络算法,图片有最小值限制,这里命名为 OFFSET
// 这里为实验值
#define OFFSET 32
if (w <= OFFSET) {
if (x - OFFSET > 0) {
x -= OFFSET;
}
w += OFFSET;
}
if (h <= OFFSET) {
if (y - OFFSET > 0) {
y -= OFFSET;
}
h += OFFSET;
}
return QRect(x, y, w, h);
}
void MainWindow::on_new_frompics() {
player->stop();
if (ensureSafeClose()) {
@ -1051,8 +962,10 @@ void MainWindow::on_saveas() {
WaitingDialog d;
d.start(tr("SaveAsGif"));
if (saveGif(filename)) {
DMessageManager::instance()->sendMessage(this, ICONRES("saveas"),
tr("SaveAsSuccess"));
d.setMessage(tr("Reprocessing"));
if (reprocessImage(filename))
DMessageManager::instance()->sendMessage(this, ICONRES("saveas"),
tr("SaveAsSuccess"));
curfilename = filename;
} else {
DMessageManager::instance()->sendMessage(this, ICONRES("saveas"),
@ -1116,12 +1029,13 @@ void MainWindow::on_close() {
}
void MainWindow::on_setting() {
DSettingsDialog *dialog = new DSettingsDialog(this);
m_settings->setSettingDialog(dialog);
dialog->updateSettings(m_settings->settings);
dialog->exec();
delete dialog;
m_settings->settings->sync();
SettingDialog dialog(m_settingd, this);
if (dialog.exec()) {
m_settingd = dialog.getResult();
m_settings->setWindowState(m_settingd.state);
m_settings->setTerminal(m_settingd.terminal);
m_settings->setReprocessingCmdline(m_settingd.cmdline);
}
}
void MainWindow::on_undo() {
@ -1185,9 +1099,11 @@ void MainWindow::on_save() {
WaitingDialog d;
d.start(tr("SaveGif"));
if (saveGif(curfilename)) {
d.setMessage(tr("Reprocessing"));
undo.setClean();
DMessageManager::instance()->sendMessage(this, ICONRES("save"),
tr("SaveSuccess"));
if (reprocessImage(curfilename))
DMessageManager::instance()->sendMessage(this, ICONRES("save"),
tr("SaveSuccess"));
} else {
DMessageManager::instance()->sendMessage(this, ICONRES("save"), "SaveFail");
}
@ -1465,10 +1381,28 @@ void MainWindow::on_wiki() {
"WingGifEditor/wiki/%E4%BB%8B%E7%BB%8D"));
}
bool MainWindow::reprocessImage(QString filename) {
if (m_settingd.cmdline.length()) {
QFileInfo info(m_settingd.terminal);
if (info.exists() && info.isExecutable()) {
if (QProcess::execute(m_settingd.terminal,
{"-c", m_settingd.cmdline.arg(filename)}) < 0) {
QMessageBox::critical(this, tr("Error"), tr("ImagingError"));
return false;
}
} else {
DMessageManager::instance()->sendMessage(this, ICONRES("icon"),
tr("InvaildTerminal"));
return false;
}
}
return true;
}
void MainWindow::closeEvent(QCloseEvent *event) {
player->stop();
if (ensureSafeClose()) {
m_settings->saveWindowState(this);
m_settings->saveWindowStatus(this);
gif.close();
event->accept();
} else

View File

@ -5,9 +5,10 @@
#include "Class/gifeditor.h"
#include "Class/playgifmanager.h"
#include "Class/settings.h"
#include "Dialog/settingdialog.h"
#include "Dialog/waitingdialog.h"
#include "GifImage/decoder/gifdecoder.h"
#include "GifImage/encoder/GifEncoder.h"
#include "GifImage/encoder/gifencoder.h"
#include "cropgifdialog.h"
#include <DGraphicsView>
#include <DLabel>
@ -36,8 +37,7 @@ private:
void setWritable(bool b);
void showGifMessage(QString message = "");
bool saveGif(QString filename);
QRect adjustImageSize(int x, int y, int w, int h);
bool reprocessImage(QString filename);
private:
void on_new_frompics();
@ -115,8 +115,7 @@ private:
QAction *undotool, *undomenu, *redotool, *redomenu;
Settings *m_settings;
QString _windowmode;
int _quality = 10;
SettingResult m_settingd;
protected:
void closeEvent(QCloseEvent *event) override;

53
Dialog/settingdialog.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "settingdialog.h"
#include <DDialogButtonBox>
#include <DLabel>
#include <QSettings>
#include <QShortcut>
SettingDialog::SettingDialog(SettingResult res, DMainWindow *parent)
: DDialog(parent) {
setWindowTitle(tr("SettingDialog"));
addContent(new DLabel(tr("WinState"), this));
addSpacing(3);
cbWinState = new DComboBox(this);
cbWinState->addItems({tr("Normal"), tr("Maximum"), tr("Minimum")});
cbWinState->setCurrentIndex(res.state);
addContent(cbWinState);
addSpacing(5);
addContent(new DLabel(tr("Terminal"), this));
addSpacing(3);
leCmd = new DLineEdit(this);
leCmd->setText(res.terminal);
addContent(leCmd);
addSpacing(5);
addContent(new DLabel(tr("Reprocessing"), this));
addSpacing(3);
cmdline = new DLineEdit(this);
cmdline->setText(res.cmdline);
addContent(cmdline);
addSpacing(20);
auto dbbox = new DDialogButtonBox(
DDialogButtonBox::Ok | DDialogButtonBox::Cancel, this);
connect(dbbox, &DDialogButtonBox::accepted, this, &SettingDialog::on_accept);
connect(dbbox, &DDialogButtonBox::rejected, this, &SettingDialog::on_reject);
auto key = QKeySequence(Qt::Key_Return);
auto s = new QShortcut(key, this);
connect(s, &QShortcut::activated, this, &SettingDialog::on_accept);
addContent(dbbox);
}
SettingResult SettingDialog::getResult() { return m_res; }
void SettingDialog::on_accept() {
m_res.state = WinState(cbWinState->currentIndex());
m_res.terminal = leCmd->text().trimmed();
m_res.cmdline = cmdline->text().trimmed();
done(1);
}
void SettingDialog::on_reject() { done(0); }
void SettingDialog::closeEvent(QCloseEvent *event) {
Q_UNUSED(event);
done(0);
}

42
Dialog/settingdialog.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef SETTINGDIALOG_H
#define SETTINGDIALOG_H
#include <DComboBox>
#include <DDialog>
#include <DLineEdit>
#include <DMainWindow>
#include <QObject>
DWIDGET_USE_NAMESPACE
enum WinState { Normal, Maximum, Minimum };
struct SettingResult {
WinState state = Normal;
QString terminal;
QString cmdline;
};
class SettingDialog : public DDialog {
Q_OBJECT
public:
explicit SettingDialog(SettingResult res, DMainWindow *parent = nullptr);
SettingResult getResult();
private:
void on_accept();
void on_reject();
protected:
void closeEvent(QCloseEvent *event) override;
private:
DLineEdit *leCmd;
DLineEdit *cmdline;
DComboBox *cbWinState;
SettingResult m_res;
};
#endif // SETTINGDIALOG_H

View File

@ -41,3 +41,8 @@ void WaitingDialog::stop() {
pro->stop();
animation->stop();
}
void WaitingDialog::setMessage(QString message) {
info->setText(message);
QApplication::processEvents();
}

View File

@ -17,6 +17,7 @@ public:
void start(QString message);
void stop();
void setMessage(QString message);
private:
DWaterProgress *pro;

View File

@ -1,11 +0,0 @@
SOURCES += \
$$PWD/decoder/gifdecoder.cpp \
$$PWD/encoder/GifEncoder.cpp \
$$PWD/encoder/algorithm/NeuQuant.cpp
HEADERS += \
$$PWD/decoder/gifdecoder.h \
$$PWD/encoder/GifEncoder.h \
$$PWD/encoder/algorithm/NeuQuant.h
include($$PWD/giflib.pri)

View File

@ -1,288 +0,0 @@
//
// Created by xiaozhuai on 2020/12/20.
//
#include "GifEncoder.h"
#include "algorithm/NeuQuant.h"
#include "gif_lib.h"
#include <cstdlib>
#include <cstring>
#include <exception>
#include <stdexcept>
#include <string>
#include <vector>
#include <QApplication>
#define GifAddExtensionBlockFor(a, func, len, data) \
GifAddExtensionBlock(&((a)->ExtensionBlockCount), &((a)->ExtensionBlocks), \
func, len, data)
static void getColorMap(uint8_t *colorMap, const uint8_t *pixels, int nPixels,
int quality) {
initnet(pixels, nPixels * 3, quality);
learn();
unbiasnet();
inxbuild();
getcolourmap(colorMap);
}
static void getRasterBits(uint8_t *rasterBits, const uint8_t *pixels,
int nPixels) {
for (int i = 0; i < nPixels; ++i) {
rasterBits[i] =
u_char(inxsearch(pixels[i * 3], pixels[i * 3 + 1], pixels[i * 3 + 2]));
}
}
inline void RGB2BGR(uint8_t *dst, const uint8_t *src, int width, int height) {
for (const uint8_t *dstEnd = dst + width * height * 3; dst < dstEnd;
src += 3) {
*(dst++) = *(src + 2);
*(dst++) = *(src + 1);
*(dst++) = *(src);
}
}
inline void BGRA2BGR(uint8_t *dst, const uint8_t *src, int width, int height) {
for (const uint8_t *dstEnd = dst + width * height * 3; dst < dstEnd;
src += 4) {
*(dst++) = *(src);
*(dst++) = *(src + 1);
*(dst++) = *(src + 2);
}
}
inline void RGBA2BGR(uint8_t *dst, const uint8_t *src, int width, int height) {
for (const uint8_t *dstEnd = dst + width * height * 3; dst < dstEnd;
src += 4) {
*(dst++) = *(src + 2);
*(dst++) = *(src + 1);
*(dst++) = *(src);
}
}
static bool convertToBGR(GifEncoder::PixelFormat format, uint8_t *dst,
const uint8_t *src, int width, int height) {
switch (format) {
case GifEncoder::PIXEL_FORMAT_BGR:
memcpy(dst, src, size_t(width * height * 3));
break;
case GifEncoder::PIXEL_FORMAT_RGB:
RGB2BGR(dst, src, width, height);
break;
case GifEncoder::PIXEL_FORMAT_BGRA:
BGRA2BGR(dst, src, width, height);
break;
case GifEncoder::PIXEL_FORMAT_RGBA:
RGBA2BGR(dst, src, width, height);
break;
default:
return false;
}
return true;
}
bool GifEncoder::open(const std::string &file, int width, int height,
int quality, bool useGlobalColorMap, int16_t loop,
int preAllocSize) {
if (m_gifFile != nullptr) {
return false;
}
int error;
m_gifFile = EGifOpenFileName(file.c_str(), false, &error);
if (!m_gifFile) {
return false;
}
m_quality = quality;
m_useGlobalColorMap = useGlobalColorMap;
reset();
if (preAllocSize > 0) {
m_framePixels = reinterpret_cast<uint8_t *>(malloc(ulong(preAllocSize)));
m_allocSize = preAllocSize;
}
m_gifFile->SWidth = width;
m_gifFile->SHeight = height;
m_gifFile->SColorResolution = 8;
m_gifFile->SBackGroundColor = 0;
m_gifFile->SColorMap = nullptr;
uint8_t appExt[11] = {'N', 'E', 'T', 'S', 'C', 'A', 'P', 'E', '2', '.', '0'};
uint8_t appExtSubBlock[3] = {
0x01, // hex 0x01
0x00, 0x00 // little-endian short. The number of times the loop should be
// executed.
};
memcpy(appExtSubBlock + 1, &loop, sizeof(loop));
GifAddExtensionBlockFor(m_gifFile, APPLICATION_EXT_FUNC_CODE, sizeof(appExt),
appExt);
GifAddExtensionBlockFor(m_gifFile, CONTINUE_EXT_FUNC_CODE,
sizeof(appExtSubBlock), appExtSubBlock);
return true;
}
bool GifEncoder::push(PixelFormat format, const uint8_t *frame, int x, int y,
int width, int height, int delay) {
if (m_gifFile == nullptr) {
return false;
}
if (frame == nullptr) {
return false;
}
if (m_useGlobalColorMap) {
if (isFirstFrame()) {
m_frameWidth = width;
m_frameHeight = height;
} else {
if (m_frameWidth != width || m_frameHeight != height) {
throw std::runtime_error(
"Frame size must be same when use global color map!");
}
}
int needSize = width * height * 3 * (m_frameCount + 1);
if (m_allocSize < needSize) {
m_framePixels =
reinterpret_cast<uint8_t *>(realloc(m_framePixels, ulong(needSize)));
m_allocSize = needSize;
}
auto *pixels = m_framePixels + width * height * 3 * m_frameCount;
convertToBGR(format, pixels, frame, width, height);
frameInfo finfo;
finfo.x = x;
finfo.y = y;
finfo.w = width;
finfo.h = height;
m_allFrames.push_back(finfo);
} else {
int needSize = width * height * 3;
if (m_allocSize < needSize) {
m_framePixels =
reinterpret_cast<uint8_t *>(realloc(m_framePixels, ulong(needSize)));
m_allocSize = needSize;
// printf("realloc 2\n");
}
auto *pixels = m_framePixels;
convertToBGR(format, pixels, frame, width, height);
auto *colorMap = GifMakeMapObject(256, nullptr);
getColorMap(reinterpret_cast<uint8_t *>(colorMap->Colors), pixels,
width * height, m_quality);
auto *rasterBits =
reinterpret_cast<GifByteType *>(malloc(ulong(width * height)));
getRasterBits(reinterpret_cast<uint8_t *>(rasterBits), pixels,
width * height);
encodeFrame(x, y, width, height, delay, colorMap, rasterBits);
}
m_frameCount++;
return true;
}
bool GifEncoder::close() {
if (m_gifFile == nullptr) {
return false;
}
ColorMapObject *globalColorMap = nullptr;
if (m_useGlobalColorMap) {
globalColorMap = GifMakeMapObject(256, nullptr);
getColorMap(reinterpret_cast<uint8_t *>(globalColorMap->Colors),
m_framePixels, m_frameWidth * m_frameHeight * m_frameCount,
m_quality);
m_gifFile->SColorMap = globalColorMap;
for (int i = 0; i < m_frameCount; ++i) {
auto *pixels = m_framePixels + m_frameWidth * m_frameHeight * 3 * i;
auto *rasterBits = reinterpret_cast<GifByteType *>(
malloc(ulong(m_frameWidth * m_frameHeight)));
getRasterBits(reinterpret_cast<uint8_t *>(rasterBits), pixels,
m_frameWidth * m_frameHeight);
auto &finfo = m_allFrames[ulong(i)];
encodeFrame(finfo.x, finfo.y, finfo.w, finfo.h, finfo.delay, nullptr,
rasterBits);
QApplication::processEvents();
}
}
int extCount = m_gifFile->ExtensionBlockCount;
auto *extBlocks = m_gifFile->ExtensionBlocks;
int savedImageCount = m_gifFile->ImageCount;
auto *savedImages = m_gifFile->SavedImages;
if (EGifSpew(m_gifFile) == GIF_ERROR) {
EGifCloseFile(reinterpret_cast<GifFileType *>(m_gifFile));
m_gifFile = nullptr;
return false;
}
if (globalColorMap != nullptr) {
GifFreeMapObject(globalColorMap);
}
GifFreeExtensions(&extCount, &extBlocks);
for (auto *sp = savedImages; sp < savedImages + savedImageCount; sp++) {
if (sp->ImageDesc.ColorMap != nullptr) {
GifFreeMapObject(sp->ImageDesc.ColorMap);
sp->ImageDesc.ColorMap = nullptr;
}
if (sp->RasterBits != nullptr) {
free(sp->RasterBits);
sp->RasterBits = nullptr;
}
GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks);
}
free(savedImages);
m_gifFile = nullptr;
reset();
return true;
}
void GifEncoder::encodeFrame(int x, int y, int width, int height, int delay,
void *colorMap, void *rasterBits) {
auto *gifImage = GifMakeSavedImage(m_gifFile, nullptr);
gifImage->ImageDesc.Left = x;
gifImage->ImageDesc.Top = y;
gifImage->ImageDesc.Width = width;
gifImage->ImageDesc.Height = height;
gifImage->ImageDesc.Interlace = false;
gifImage->ImageDesc.ColorMap = reinterpret_cast<ColorMapObject *>(colorMap);
gifImage->RasterBits = reinterpret_cast<GifByteType *>(rasterBits);
gifImage->ExtensionBlockCount = 0;
gifImage->ExtensionBlocks = nullptr;
GraphicsControlBlock gcb;
gcb.DisposalMode = DISPOSE_DO_NOT;
gcb.UserInputFlag = false;
gcb.DelayTime = delay;
gcb.TransparentColor = NO_TRANSPARENT_COLOR;
uint8_t gcbBytes[4];
EGifGCBToExtension(&gcb, gcbBytes);
GifAddExtensionBlockFor(gifImage, GRAPHICS_EXT_FUNC_CODE, sizeof(gcbBytes),
gcbBytes);
}

View File

@ -1,103 +0,0 @@
//
// Created by xiaozhuai on 2020/12/20.
//
#ifndef GIF_GIFENCODER_H
#define GIF_GIFENCODER_H
#include "gif_lib.h"
#include <string>
#include <vector>
class GifEncoder {
public:
enum PixelFormat {
PIXEL_FORMAT_UNKNOWN = 0,
PIXEL_FORMAT_BGR = 1,
PIXEL_FORMAT_RGB = 2,
PIXEL_FORMAT_BGRA = 3,
PIXEL_FORMAT_RGBA = 4,
};
public:
GifEncoder() = default;
/**
* create gif file
*
* @param file file path
* @param width gif width
* @param height gif height
* @param quality 1..30, 1 is best
* @param useGlobalColorMap
* @param loop loop count, 0 is endless
* @param For better performance, it's suggested to set preAllocSize. If you
* can't determine it, set to 0. If use global color map, all frames size must
* be same, and preAllocSize = width * height * 3 * nFrame If use local color
* map, preAllocSize = MAX(width * height) * 3
* @return
*/
bool open(const std::string &file, int width, int height, int quality,
bool useGlobalColorMap, int16_t loop, int preAllocSize = 0);
/**
* add frame
*
* @param format pixel format
* @param frame frame data
* @param width frame width
* @param height frame height
* @param delay delay time 0.01s
* @return
*/
bool push(PixelFormat format, const uint8_t *frame, int x, int y, int width,
int height, int delay);
/**
* close gif file
*
* @return
*/
bool close();
private:
inline bool isFirstFrame() const { return m_frameCount == 0; }
inline void reset() {
if (m_framePixels != nullptr) {
free(m_framePixels);
m_framePixels = nullptr;
}
m_allocSize = 0;
m_allFrames.clear();
m_frameCount = 0;
m_frameWidth = -1;
m_frameHeight = -1;
}
void encodeFrame(int x, int y, int width, int height, int delay,
void *colorMap, void *rasterBits);
private:
struct frameInfo {
int delay;
int x;
int y;
int w;
int h;
};
GifFileType *m_gifFile = nullptr;
int m_quality = 10;
bool m_useGlobalColorMap = false;
uint8_t *m_framePixels = nullptr;
int m_allocSize = 0;
std::vector<frameInfo> m_allFrames{};
int m_frameCount = 0;
int m_frameWidth = -1;
int m_frameHeight = -1;
};
#endif // GIF_GIFENCODER_H

21
GifImage/encoder/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Daniel Löbl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,72 @@
## CGIF, a GIF encoder written in C
A fast and lightweight GIF encoder that can create GIF animations and images. Summary of the main features:
- user-defined global or local color-palette with up to 256 colors (limit of the GIF format)
- size-optimizations for GIF animations:
- option to set a pixel to transparent if it has identical color in the previous frame (transparency optimization)
- do encoding just for the rectangular area that differs from the previous frame (width/height optimization)
- fast: a GIF with 256 colors and 1024x1024 pixels can be created in below 50 ms even on a minimalistic system
- MIT license (permissive)
- different options for GIF animations: static image, N repetitions, infinite repetitions
- additional source-code for verifying the encoder after making changes
- user-defined delay time from one frame to the next (can be set independently for each frame)
- source-code conforms to the C99 standard
## Examples
To get started, we suggest that you have a look at our code examples. ```examples/cgif_example_video.c``` is an example that creates a GIF animation. ```examples/cgif_example.c``` is an example for a static GIF image.
## Overview
To get an overview of the API, we recommend having a look at our wiki (https://github.com/dloebl/cgif/wiki/General-API) where types and functions are described. The corresponding implementations can be found in ```src/cgif.c``` and ```src/cgif_raw.c```. Here the most important types and functions:
```C
// These are the four struct types that contain all GIF data and parameters:
typedef CGIF_Config // global cofinguration parameters of the GIF
typedef CGIF_FrameConfig // local configuration parameters for a frame
typedef CGIF // struct for the full GIF
typedef CGIF_Frame // struct for a single frame
// The user needs only these three functions to create a GIF image:
CGIF* cgif_newgif (CGIF_Config* pConfig); // creates a new GIF
int cgif_addframe (CGIF* pGIF, CGIF_FrameConfig* pConfig); // adds a frame to an existing GIF
int cgif_close (CGIF* pGIF); // close the created file and free memory
```
With our encoder you can create animated or static GIFs, you can or cannot use certain optimizations, and so on. You can switch between all these different options easily using the two attributes ```attrFlags``` and ```genFlags``` in the configurations ```CGIF_Config``` and ```CGIF_FrameConfig```. These attributes are of type ```uint32_t``` and bundle yes/no-options with a bit-wise logic. So far only a few of the 32 bits are used leaving space to include further functionalities ensuring backward compatibility. We provide the following flag settings which can be combined by bit-wise or-operations:
```C
CGIF_ATTR_IS_ANIMATED // make an animated GIF (default is non-animated GIF)
CGIF_ATTR_NO_GLOBAL_TABLE // disable global color table (global color table is default)
CGIF_ATTR_HAS_TRANSPARENCY // first entry in color table contains transparency (alpha channel)
CGIF_ATTR_NO_LOOP // run GIF animation only one time. numLoops is ignored (no repetitions)
CGIF_FRAME_ATTR_USE_LOCAL_TABLE // use a local color table for a frame (not used by default)
CGIF_FRAME_ATTR_HAS_ALPHA // frame contains alpha channel (index set via transIndex field)
CGIF_FRAME_ATTR_HAS_SET_TRANS // transparency setting provided by user (transIndex field)
CGIF_FRAME_ATTR_INTERLACED // encode frame interlaced
CGIF_FRAME_GEN_USE_TRANSPARENCY // use transparency optimization (size optimization)
CGIF_FRAME_GEN_USE_DIFF_WINDOW // do encoding just for the sub-window that changed (size optimization)
```
If you didn't understand the point of ```attrFlags``` and ```genFlags``` and the flags, please don't worry. The example files ```examples/cgif_example.c``` and ```examples/cgif_example_video.c``` are all you need to get started and the used default settings for ```attrFlags``` and ```genFlags``` cover most cases quite well.
## Compiling the example
An example can be compiled and tested simply by:
```
$ c99 -o cgif_example -Iinc examples/cgif_example_video.c src/cgif.c src/cgif_raw.c
$ ./cgif_example
```
## Validating the encoder
In the folder ```tests```, we provide several testing routines that you can run via the script ```tests/performtests.sh```. To perform the tests you need to install the programs [ImageMagick](https://github.com/ImageMagick/ImageMagick), [gifsicle](https://github.com/kohler/gifsicle) and [tcc (tiny c compiler)](https://bellard.org/tcc/).
With the provided tests you can validate that the encoder still generates correct GIF files after making changes on the encoder itself.
## Further explanations
The GIF format employs the [Lempel-Ziv-Welch (LZW)](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch) algorithm for image compression. If you are interested in details of the GIF format, please have a look at the official GIF documentation (https://www.w3.org/Graphics/GIF/spec-gif89a.txt).
## Versioning scheme
Releases of cgif follow the semantic versioning scheme as described here: [semver.org](https://semver.org/)
The following additional guarantees are provided:
* Public API of versions 0.x.x are stable.
## License
Licensed under the MIT license (permissive).
For more details please see ```LICENSE```

View File

@ -1,436 +0,0 @@
/* NeuQuant Neural-Net Quantization Algorithm
* ------------------------------------------
*
* Copyright (c) 1994 Anthony Dekker
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
* See "Kohonen neural networks for optimal colour quantization"
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
* for a discussion of the algorithm.
* See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
* in this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons who receive
* copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*/
#include "NeuQuant.h"
/* Network Definitions
------------------- */
#define maxnetpos 255
#define netbiasshift 4 /* bias for colour values */
#define ncycles 100 /* no. of learning cycles */
/* defs for freq and bias */
#define intbiasshift 16 /* bias for fractions */
#define intbias 65536
#define gammashift 10 /* gamma = 1024 */
#define gamma 1024
#define betashift 10
#define beta 64 /* beta = 1/1024 */
#define betagamma 65536
/* defs for decreasing radius factor */
#define initrad 32 /* for 256 cols, radius starts */
#define radiusbiasshift 6 /* at 32.0 biased by 6 bits */
#define radiusbias 64
#define initradius 2048 /* and decreases by a */
#define radiusdec 30 /* factor of 1/30 each cycle */
/* defs for decreasing alpha factor */
#define alphabiasshift 10 /* alpha starts at 1.0 */
#define initalpha 1024
int alphadec; /* biased by 10 bits */
/* radbias and alpharadbias used for radpower calculation */
#define radbiasshift 8
#define radbias 256
#define alpharadbshift 18
#define alpharadbias 262144
/* Types and Global Variables
-------------------------- */
static const unsigned char *thepicture; /* the input image itself */
static int lengthcount; /* lengthcount = H*W*3 */
static int samplefac; /* sampling factor 1..30 */
typedef int pixel[4]; /* BGRc */
static pixel network[netsize]; /* the network itself */
static int netindex[256]; /* for network lookup - really 256 */
static int bias[netsize]; /* bias and freq arrays for learning */
static int freq[netsize];
static int radpower[initrad]; /* radpower for precomputation */
int getNetwork(int i, int j) {
return network[i][j];
}
/* Initialise network in range (0,0,0) to (255,255,255) and set parameters
----------------------------------------------------------------------- */
void initnet(const unsigned char *thepic, int len, int sample) {
int i;
int *p;
thepicture = thepic;
lengthcount = len;
samplefac = sample;
for (i = 0; i < netsize; i++) {
p = network[i];
p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize;
freq[i] = intbias / netsize; /* 1/netsize */
bias[i] = 0;
}
}
/* Unbias network to give byte values 0..255 and record position i to prepare for sort
----------------------------------------------------------------------------------- */
void unbiasnet() {
int i, j, temp;
for (i = 0; i < netsize; i++) {
for (j = 0; j < 3; j++) {
/* OLD CODE: network[i][j] >>= netbiasshift; */
/* Fix based on bug report by Juergen Weigert jw@suse.de */
temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;
if (temp > 255) temp = 255;
network[i][j] = temp;
}
network[i][3] = i; /* record colour no */
}
}
/* Output colour map
----------------- */
void writecolourmap(FILE *f) {
int i, j;
for (i = 2; i >= 0; i--)
for (j = 0; j < netsize; j++)
putc(network[j][i], f);
}
void getcolourmap(uint8_t *colorMap) {
int *index = new int[netsize];
for (int i = 0; i < netsize; i++)
index[network[i][3]] = i;
int k = 0;
for (int i = 0; i < netsize; i++) {
int j = index[i];
colorMap[k++] = network[j][2];
colorMap[k++] = network[j][1];
colorMap[k++] = network[j][0];
}
delete[] index;
}
/* Insertion sort of network and building of netindex[0..255] (to do after unbias)
------------------------------------------------------------------------------- */
void inxbuild() {
int i, j, smallpos, smallval;
int *p, *q;
int previouscol, startpos;
previouscol = 0;
startpos = 0;
for (i = 0; i < netsize; i++) {
p = network[i];
smallpos = i;
smallval = p[1]; /* index on g */
/* find smallest in i..netsize-1 */
for (j = i + 1; j < netsize; j++) {
q = network[j];
if (q[1] < smallval) { /* index on g */
smallpos = j;
smallval = q[1]; /* index on g */
}
}
q = network[smallpos];
/* swap p (i) and q (smallpos) entries */
if (i != smallpos) {
j = q[0];
q[0] = p[0];
p[0] = j;
j = q[1];
q[1] = p[1];
p[1] = j;
j = q[2];
q[2] = p[2];
p[2] = j;
j = q[3];
q[3] = p[3];
p[3] = j;
}
/* smallval entry is now in position i */
if (smallval != previouscol) {
netindex[previouscol] = (startpos + i) >> 1;
for (j = previouscol + 1; j < smallval; j++) netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */
}
/* Search for BGR values 0..255 (after net is unbiased) and return colour index
---------------------------------------------------------------------------- */
int inxsearch(int b, int g, int r) {
int i, j, dist, a, bestd;
int *p;
int best;
bestd = 1000; /* biggest possible dist is 256*3 */
best = -1;
i = netindex[g]; /* index on g */
j = i - 1; /* start at netindex[g] and work outwards */
while ((i < netsize) || (j >= 0)) {
if (i < netsize) {
p = network[i];
dist = p[1] - g; /* inx key */
if (dist >= bestd) i = netsize; /* stop iter */
else {
i++;
if (dist < 0) dist = -dist;
a = p[0] - b;
if (a < 0) a = -a;
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0) a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
if (j >= 0) {
p = network[j];
dist = g - p[1]; /* inx key - reverse dif */
if (dist >= bestd) j = -1; /* stop iter */
else {
j--;
if (dist < 0) dist = -dist;
a = p[0] - b;
if (a < 0) a = -a;
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0) a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
}
return (best);
}
/* Search for biased BGR values
---------------------------- */
int contest(int b, int g, int r) {
/* finds closest neuron (min dist) and updates freq */
/* finds best neuron (min dist-bias) and returns position */
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */
/* bias[i] = gamma*((1/netsize)-freq[i]) */
int i, dist, a, biasdist, betafreq;
int bestpos, bestbiaspos, bestd, bestbiasd;
int *p, *f, *n;
bestd = ~(((int) 1) << 31);
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
p = bias;
f = freq;
for (i = 0; i < netsize; i++) {
n = network[i];
dist = n[0] - b;
if (dist < 0) dist = -dist;
a = n[1] - g;
if (a < 0) a = -a;
dist += a;
a = n[2] - r;
if (a < 0) a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
bestpos = i;
}
biasdist = dist - ((*p) >> (intbiasshift - netbiasshift));
if (biasdist < bestbiasd) {
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (*f >> betashift);
*f++ -= betafreq;
*p++ += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return (bestbiaspos);
}
/* Move neuron i towards biased (b,g,r) by factor alpha
---------------------------------------------------- */
void altersingle(int alpha, int i, int b, int g, int r) {
int *n;
// printf("New point %d: ", i);
n = network[i]; /* alter hit neuron */
*n -= (alpha * (*n - b)) / initalpha;
// printf("%f, ", *n / 16.0);
n++;
*n -= (alpha * (*n - g)) / initalpha;
// printf("%f, ", *n / 16.0);
n++;
*n -= (alpha * (*n - r)) / initalpha;
// printf("%f\n", *n / 16.0);
}
/* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
--------------------------------------------------------------------------------- */
void alterneigh(int rad, int i, int b, int g, int r) {
int j, k, lo, hi, a;
int *p, *q;
lo = i - rad;
if (lo < -1) lo = -1;
hi = i + rad;
if (hi > netsize) hi = netsize;
j = i + 1;
k = i - 1;
q = radpower;
while ((j < hi) || (k > lo)) {
a = (*(++q));
if (j < hi) {
// printf("New point %d: ", j);
p = network[j];
*p -= (a * (*p - b)) / alpharadbias;
// printf("%f, ", *p / 16.0);
p++;
*p -= (a * (*p - g)) / alpharadbias;
// printf("%f, ", *p / 16.0);
p++;
*p -= (a * (*p - r)) / alpharadbias;
// printf("%f\n", *p / 16.0);
j++;
}
if (k > lo) {
// printf("New point %d: ", k);
p = network[k];
*p -= (a * (*p - b)) / alpharadbias;
// printf("%f, ", *p / 16.0);
p++;
*p -= (a * (*p - g)) / alpharadbias;
// printf("%f, ", *p / 16.0);
p++;
*p -= (a * (*p - r)) / alpharadbias;
// printf("%f\n", *p / 16.0);
k--;
}
}
}
/* Main Learning Loop
------------------ */
void learn() {
int i, j, b, g, r;
int radius, rad, alpha, step, delta, samplepixels;
const unsigned char *p;
const unsigned char *lim;
alphadec = 30 + ((samplefac - 1) / 3);
p = thepicture;
lim = thepicture + lengthcount;
samplepixels = lengthcount / (3 * samplefac);
delta = samplepixels / ncycles;
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1) rad = 0;
for (i = 0; i < rad; i++)
radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));
// fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad);
if ((lengthcount % prime1) != 0) step = 3 * prime1;
else {
if ((lengthcount % prime2) != 0) step = 3 * prime2;
else {
if ((lengthcount % prime3) != 0) step = 3 * prime3;
else step = 3 * prime4;
}
}
i = 0;
while (i < samplepixels) {
b = p[0] << netbiasshift;
g = p[1] << netbiasshift;
r = p[2] << netbiasshift;
j = contest(b, g, r);
altersingle(alpha, j, b, g, r);
if (rad) alterneigh(rad, j, b, g, r); /* alter neighbours */
p += step;
if (p >= lim) p -= lengthcount;
i++;
if (i % delta == 0) {
alpha -= alpha / alphadec;
radius -= radius / radiusdec;
rad = radius >> radiusbiasshift;
if (rad <= 1) rad = 0;
for (j = 0; j < rad; j++)
radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));
}
}
// fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha);
}

View File

@ -1,83 +0,0 @@
/* NeuQuant Neural-Net Quantization Algorithm Interface
* ----------------------------------------------------
*
* Copyright (c) 1994 Anthony Dekker
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
* See "Kohonen neural networks for optimal colour quantization"
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
* for a discussion of the algorithm.
* See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
* in this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons who receive
* copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*/
#pragma once
#include <cstdio>
#include <cstdint>
#define netsize 256 /* number of colours used */
/* For 256 colours, fixed arrays need 8kb, plus space for the image
---------------------------------------------------------------- */
/* four primes near 500 - assume no image has a length so large */
/* that it is divisible by all four primes */
#define prime1 499
#define prime2 491
#define prime3 487
#define prime4 503
#define minpicturebytes (3*prime4) /* minimum size for input image */
int getNetwork(int i, int j);
/* Initialise network in range (0,0,0) to (255,255,255) and set parameters
----------------------------------------------------------------------- */
void initnet(const unsigned char *thepic, int len, int sample);
/* Unbias network to give byte values 0..255 and record position i to prepare for sort
----------------------------------------------------------------------------------- */
void unbiasnet(); /* can edit this function to do output of colour map */
/* Output colour map
----------------- */
void writecolourmap(FILE *f);
void getcolourmap(uint8_t *colorMap);
/* Insertion sort of network and building of netindex[0..255] (to do after unbias)
------------------------------------------------------------------------------- */
void inxbuild();
/* Search for BGR values 0..255 (after net is unbiased) and return colour index
---------------------------------------------------------------------------- */
int inxsearch(int b, int g, int r);
/* Main Learning Loop
------------------ */
void learn();
/* Program Skeleton
----------------
[select samplefac in range 1..30]
pic = (unsigned char*) malloc(3*width*height);
[read image from input file into pic]
initnet(pic,3*width*height,samplefac);
learn();
unbiasnet();
[write output image header, using writecolourmap(f),
possibly editing the loops in that function]
inxbuild();
[write output image using inxsearch(b,g,r)] */

View File

@ -0,0 +1,36 @@
#include "gifencoder.h"
#include <QImage>
GifEncoder::GifEncoder(QObject *parent) : QObject(parent) {}
bool GifEncoder::open(QString filename, int width, int height) {
CGIFrgb_Config config;
memset(&config, 0, sizeof(config));
config.path = filename.toLocal8Bit().constData();
config.width = uint16_t(width);
config.height = uint16_t(height);
pGIF = cgif_rgb_newgif(&config);
return pGIF;
}
bool GifEncoder::push(QImage &image, int delayTime) {
if (pGIF == nullptr) {
return false;
}
CGIFrgb_FrameConfig fconfig;
memset(&fconfig, 0, sizeof(fconfig));
fconfig.pImageData = reinterpret_cast<uint8_t *>(
const_cast<unsigned char *>(image.constBits()));
fconfig.fmtChan = CGIF_CHAN_FMT_RGBA;
fconfig.delay = uint16_t(delayTime);
cgif_rgb_addframe(pGIF, &fconfig);
return true;
}
bool GifEncoder::close() {
if (cgif_rgb_close(pGIF) == CGIF_OK) {
pGIF = nullptr;
return true;
}
return false;
}

View File

@ -0,0 +1,21 @@
#ifndef GIFENCODER_H
#define GIFENCODER_H
#include "GifImage/encoder/inc/cgif.h"
#include <QObject>
class GifEncoder : public QObject {
Q_OBJECT
public:
explicit GifEncoder(QObject *parent = nullptr);
public slots:
bool open(QString filename, int width, int height);
bool push(QImage &image, int delayTime);
bool close();
private:
CGIFrgb *pGIF = nullptr;
};
#endif // GIFENCODER_H

111
GifImage/encoder/inc/cgif.h Normal file
View File

@ -0,0 +1,111 @@
#ifndef CGIF_H
#define CGIF_H
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
// flags to set the GIF/frame-attributes
#define CGIF_ATTR_IS_ANIMATED (1uL << 1) // make an animated GIF (default is non-animated GIF)
#define CGIF_ATTR_NO_GLOBAL_TABLE (1uL << 2) // disable global color table (global color table is default)
#define CGIF_ATTR_HAS_TRANSPARENCY (1uL << 3) // first entry in color table contains transparency (alpha channel)
#define CGIF_ATTR_NO_LOOP (1uL << 4) // don't loop a GIF animation: only play it one time.
#define CGIF_FRAME_ATTR_USE_LOCAL_TABLE (1uL << 0) // use a local color table for a frame (local color table is not used by default)
#define CGIF_FRAME_ATTR_HAS_ALPHA (1uL << 1) // alpha channel index provided by user (transIndex field)
#define CGIF_FRAME_ATTR_HAS_SET_TRANS (1uL << 2) // transparency setting provided by user (transIndex field)
#define CGIF_FRAME_ATTR_INTERLACED (1uL << 3) // encode frame interlaced (default is not interlaced)
// flags to decrease GIF-size
#define CGIF_FRAME_GEN_USE_TRANSPARENCY (1uL << 0) // use transparency optimization (setting pixels identical to previous frame transparent)
#define CGIF_FRAME_GEN_USE_DIFF_WINDOW (1uL << 1) // do encoding just for the sub-window that has changed from previous frame
#define CGIF_INFINITE_LOOP (0x0000uL) // for animated GIF: 0 specifies infinite loop
typedef enum {
CGIF_ERROR = -1, // something unspecified failed
CGIF_OK = 0, // everything OK
CGIF_EWRITE, // writing GIF data failed
CGIF_EALLOC, // allocating memory failed
CGIF_ECLOSE, // final call to fclose failed
CGIF_EOPEN, // failed to open output file
CGIF_EINDEX, // invalid index in image data provided by user
// internal section (values subject to change)
CGIF_PENDING,
} cgif_result;
typedef enum {
CGIF_CHAN_FMT_RGB = 3, // 3 byte per pixel (red, green, blue)
CGIF_CHAN_FMT_RGBA = 4, // 4 byte per pixel (red, green, blue, alpha)
} cgif_chan_fmt;
typedef struct st_gif CGIF; // struct for the full GIF
typedef struct st_gifconfig CGIF_Config; // global cofinguration parameters of the GIF
typedef struct st_frameconfig CGIF_FrameConfig; // local configuration parameters for a frame
typedef struct st_cgif_rgb_config CGIFrgb_Config;
typedef struct st_cgif_rgb CGIFrgb;
typedef struct st_cgif_rgb_frameconfig CGIFrgb_FrameConfig;
typedef int cgif_write_fn(void* pContext, const uint8_t* pData, const size_t numBytes); // callback function for stream-based output
// prototypes
CGIF* cgif_newgif (CGIF_Config* pConfig); // creates a new GIF (returns pointer to new GIF or NULL on error)
int cgif_addframe (CGIF* pGIF, CGIF_FrameConfig* pConfig); // adds the next frame to an existing GIF (returns 0 on success)
int cgif_close (CGIF* pGIF); // close file and free allocated memory (returns 0 on success)
CGIFrgb* cgif_rgb_newgif (const CGIFrgb_Config* pConfig);
cgif_result cgif_rgb_addframe (CGIFrgb* pGIF, const CGIFrgb_FrameConfig* pConfig);
cgif_result cgif_rgb_close (CGIFrgb* pGIF);
// CGIF_Config type (parameters passed by user)
// note: must stay AS IS for backward compatibility
struct st_gifconfig {
uint8_t* pGlobalPalette; // global color table of the GIF
const char* path; // path of the GIF to be created, mutually exclusive with pWriteFn
uint32_t attrFlags; // fixed attributes of the GIF (e.g. whether it is animated or not)
uint32_t genFlags; // flags that determine how the GIF is generated (e.g. optimization)
uint16_t width; // width of each frame in the GIF
uint16_t height; // height of each frame in the GIF
uint16_t numGlobalPaletteEntries; // size of the global color table
uint16_t numLoops; // number of repetitons of an animated GIF (set to INFINITE_LOOP for infinite loop)
cgif_write_fn *pWriteFn; // callback function for chunks of output data, mutually exclusive with path
void* pContext; // opaque pointer passed as the first parameter to pWriteFn
};
// CGIF_FrameConfig type (parameters passed by user)
// note: must stay AS IS for backward compatibility
struct st_frameconfig {
uint8_t* pLocalPalette; // local color table of a frame
uint8_t* pImageData; // image data to be encoded
uint32_t attrFlags; // fixed attributes of the GIF frame
uint32_t genFlags; // flags that determine how the GIF frame is created (e.g. optimization)
uint16_t delay; // delay before the next frame is shown (units of 0.01 s)
uint16_t numLocalPaletteEntries; // size of the local color table
uint8_t transIndex; // introduced with V0.2.0
};
struct st_cgif_rgb_config {
cgif_write_fn* pWriteFn;
void* pContext;
const char* path;
uint32_t attrFlags;
uint32_t genFlags;
uint16_t numLoops;
uint16_t width;
uint16_t height;
};
struct st_cgif_rgb_frameconfig {
uint8_t* pImageData;
cgif_chan_fmt fmtChan;
uint32_t attrFlags; // TBD
uint32_t genFlags; // TBD
uint16_t delay;
};
#ifdef __cplusplus
}
#endif
#endif // CGIF_H

View File

@ -0,0 +1,69 @@
#ifndef CGIF_RAW_H
#define CGIF_RAW_H
#include <stdint.h>
#include "cgif.h"
#ifdef __cplusplus
extern "C" {
#endif
#define DISPOSAL_METHOD_LEAVE (1uL << 2)
#define DISPOSAL_METHOD_BACKGROUND (2uL << 2)
#define DISPOSAL_METHOD_PREVIOUS (3uL << 2)
// flags to set the GIF attributes
#define CGIF_RAW_ATTR_IS_ANIMATED (1uL << 0) // make an animated GIF (default is non-animated GIF)
#define CGIF_RAW_ATTR_NO_LOOP (1uL << 1) // don't loop a GIF animation: only play it one time.
// flags to set the Frame attributes
#define CGIF_RAW_FRAME_ATTR_HAS_TRANS (1uL << 0) // provided transIndex should be set
#define CGIF_RAW_FRAME_ATTR_INTERLACED (1uL << 1) // encode frame interlaced
// CGIFRaw_Config type
// note: internal sections, subject to change.
typedef struct {
cgif_write_fn *pWriteFn; // callback function for chunks of output data
void* pContext; // opaque pointer passed as the first parameter to pWriteFn
uint8_t* pGCT; // global color table of the GIF
uint32_t attrFlags; // fixed attributes of the GIF (e.g. whether it is animated or not)
uint16_t width; // effective width of each frame in the GIF
uint16_t height; // effective height of each frame in the GIF
uint16_t sizeGCT; // size of the global color table (GCT)
uint16_t numLoops; // number of repetitons of an animated GIF (set to INFINITE_LOOP resp. 0 for infinite loop, use CGIF_ATTR_NO_LOOP if you don't want any repetition)
} CGIFRaw_Config;
// CGIFRaw_FrameConfig type
// note: internal sections, subject to chage.
typedef struct {
uint8_t* pLCT; // local color table of the frame (LCT)
uint8_t* pImageData; // image data to be encoded (indices to CT)
uint32_t attrFlags; // fixed attributes of the GIF frame
uint16_t width; // width of frame
uint16_t height; // height of frame
uint16_t top; // top offset of frame
uint16_t left; // left offset of frame
uint16_t delay; // delay before the next frame is shown (units of 0.01 s [cs])
uint16_t sizeLCT; // size of the local color table (LCT)
uint8_t disposalMethod; // specifies how this frame should be disposed after being displayed.
uint8_t transIndex; // transparency index
} CGIFRaw_FrameConfig;
// CGIFRaw type
// note: internal sections, subject to change.
typedef struct {
CGIFRaw_Config config; // configutation parameters of the GIF (see above)
cgif_result curResult; // current result status of GIFRaw stream
} CGIFRaw;
// prototypes
CGIFRaw* cgif_raw_newgif (const CGIFRaw_Config* pConfig);
cgif_result cgif_raw_addframe (CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig);
cgif_result cgif_raw_close (CGIFRaw* pGIF);
#ifdef __cplusplus
}
#endif
#endif // CGIF_RAW_H

494
GifImage/encoder/src/cgif.c Normal file
View File

@ -0,0 +1,494 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cgif.h"
#include "cgif_raw.h"
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
#define SIZE_FRAME_QUEUE (3)
// CGIF_Frame type
// note: internal sections, subject to change in future versions
typedef struct {
CGIF_FrameConfig config;
uint8_t disposalMethod;
uint8_t transIndex;
} CGIF_Frame;
// CGIF type
// note: internal sections, subject to change in future versions
struct st_gif {
CGIF_Frame* aFrames[SIZE_FRAME_QUEUE]; // (internal) we need to keep the last three frames in memory.
CGIF_Config config; // (internal) configuration parameters of the GIF
CGIFRaw* pGIFRaw; // (internal) raw GIF stream
FILE* pFile;
cgif_result curResult;
};
// dimension result type
typedef struct {
uint16_t width;
uint16_t height;
uint16_t top;
uint16_t left;
} DimResult;
/* calculate next power of two exponent of given number (n MUST be <= 256) */
static uint8_t calcNextPower2Ex(uint16_t n) {
uint8_t nextPow2;
for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
return nextPow2;
}
/* write callback. returns 0 on success or -1 on error. */
static int writecb(void* pContext, const uint8_t* pData, const size_t numBytes) {
CGIF* pGIF;
size_t r;
pGIF = (CGIF*)pContext;
if(pGIF->pFile) {
r = fwrite(pData, 1, numBytes, pGIF->pFile);
if(r == numBytes) return 0;
else return -1;
} else if(pGIF->config.pWriteFn) {
return pGIF->config.pWriteFn(pGIF->config.pContext, pData, numBytes);
}
return 0;
}
/* free space allocated for CGIF struct */
static void freeCGIF(CGIF* pGIF) {
if((pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
free(pGIF->config.pGlobalPalette);
}
free(pGIF);
}
/* create a new GIF */
CGIF* cgif_newgif(CGIF_Config* pConfig) {
FILE* pFile;
CGIF* pGIF;
CGIFRaw* pGIFRaw; // raw GIF stream
CGIFRaw_Config rawConfig = {0};
pFile = NULL;
// open output file (if necessary)
if(pConfig->path) {
pFile = fopen(pConfig->path, "wb");
if(pFile == NULL) {
return NULL; // error: fopen failed
}
}
// allocate space for CGIF context
pGIF = malloc(sizeof(CGIF));
if(pGIF == NULL) {
if(pFile) {
fclose(pFile);
}
return NULL; // error -> malloc failed
}
memset(pGIF, 0, sizeof(CGIF));
pGIF->pFile = pFile;
memcpy(&(pGIF->config), pConfig, sizeof(CGIF_Config));
// make a deep copy of global color tabele (GCT), if required.
if((pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) == 0) {
pGIF->config.pGlobalPalette = malloc(pConfig->numGlobalPaletteEntries * 3);
memcpy(pGIF->config.pGlobalPalette, pConfig->pGlobalPalette, pConfig->numGlobalPaletteEntries * 3);
}
rawConfig.pGCT = pConfig->pGlobalPalette;
rawConfig.sizeGCT = (pConfig->attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE) ? 0 : pConfig->numGlobalPaletteEntries;
// translate CGIF_ATTR_* to CGIF_RAW_ATTR_* flags
rawConfig.attrFlags = (pConfig->attrFlags & CGIF_ATTR_IS_ANIMATED) ? CGIF_RAW_ATTR_IS_ANIMATED : 0;
rawConfig.attrFlags |= (pConfig->attrFlags & CGIF_ATTR_NO_LOOP) ? CGIF_RAW_ATTR_NO_LOOP : 0;
rawConfig.width = pConfig->width;
rawConfig.height = pConfig->height;
rawConfig.numLoops = pConfig->numLoops;
rawConfig.pWriteFn = writecb;
rawConfig.pContext = (void*)pGIF;
// pass config down and create a new raw GIF stream.
pGIFRaw = cgif_raw_newgif(&rawConfig);
// check for errors
if(pGIFRaw == NULL) {
if(pFile) {
fclose(pFile);
}
freeCGIF(pGIF);
return NULL;
}
pGIF->pGIFRaw = pGIFRaw;
// assume error per default.
// set to CGIF_OK by the first successful cgif_addframe() call, as a GIF without frames is invalid.
pGIF->curResult = CGIF_PENDING;
return pGIF;
}
/* compare given pixel indices using the correct local or global color table; returns 0 if the two pixels are RGB equal */
static int cmpPixel(const CGIF* pGIF, const CGIF_FrameConfig* pCur, const CGIF_FrameConfig* pBef, const uint8_t iCur, const uint8_t iBef) {
uint8_t* pBefCT; // color table to use for pBef
uint8_t* pCurCT; // color table to use for pCur
if((pCur->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iCur == pCur->transIndex) {
return 0; // identical
}
if((pBef->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) && iBef == pBef->transIndex) {
return 1; // done: cannot compare
}
// safety bounds check
const uint16_t sizeCTBef = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
const uint16_t sizeCTCur = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
if((iBef >= sizeCTBef) || (iCur >= sizeCTCur)) {
return 1; // error: out-of-bounds - cannot compare
}
pBefCT = (pBef->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pBef->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
pCurCT = (pCur->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? pCur->pLocalPalette : pGIF->config.pGlobalPalette; // local or global table used?
return memcmp(pBefCT + iBef * 3, pCurCT + iCur * 3, 3);
}
/* optimize GIF file size by only redrawing the rectangular area that differs from previous frame */
static uint8_t* doWidthHeightOptim(CGIF* pGIF, CGIF_FrameConfig* pCur, CGIF_FrameConfig* pBef, DimResult* pResult) {
uint8_t* pNewImageData;
const uint8_t* pCurImageData;
const uint8_t* pBefImageData;
uint16_t i, x;
uint16_t newHeight, newWidth, newLeft, newTop;
const uint16_t width = pGIF->config.width;
const uint16_t height = pGIF->config.height;
uint8_t iCur, iBef;
pCurImageData = pCur->pImageData;
pBefImageData = pBef->pImageData;
// find top
i = 0;
while(i < height) {
for(int c = 0; c < width; ++c) {
iCur = *(pCurImageData + MULU16(i, width) + c);
iBef = *(pBefImageData + MULU16(i, width) + c);
if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
goto FoundTop;
}
}
++i;
}
FoundTop:
if(i == height) { // need dummy pixel (frame is identical with one before)
// TBD we might make it possible to merge identical frames in the future
newWidth = 1;
newHeight = 1;
newLeft = 0;
newTop = 0;
goto Done;
}
newTop = i;
// find actual height
i = height - 1;
while(i > newTop) {
for(int c = 0; c < width; ++c) {
iCur = *(pCurImageData + MULU16(i, width) + c);
iBef = *(pBefImageData + MULU16(i, width) + c);
if(cmpPixel(pGIF, pCur, pBef, iCur, iBef) != 0) {
goto FoundHeight;
}
}
--i;
}
FoundHeight:
newHeight = (i + 1) - newTop;
// find left
i = newTop;
x = 0;
while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
++i;
if(i > (newTop + newHeight - 1)) {
++x; //(x==width cannot happen as goto Done is triggered in the only possible case before)
i = newTop;
}
}
newLeft = x;
// find actual width
i = newTop;
x = width - 1;
while(cmpPixel(pGIF, pCur, pBef, pCurImageData[MULU16(i, width) + x], pBefImageData[MULU16(i, width) + x]) == 0) {
++i;
if(i > (newTop + newHeight - 1)) {
--x; //(x<newLeft cannot happen as goto Done is triggered in the only possible case before)
i = newTop;
}
}
newWidth = (x + 1) - newLeft;
Done:
// create new image data
pNewImageData = malloc(MULU16(newWidth, newHeight)); // TBD check return value of malloc
for (i = 0; i < newHeight; ++i) {
memcpy(pNewImageData + MULU16(i, newWidth), pCurImageData + MULU16((i + newTop), width) + newLeft, newWidth);
}
// set new width, height, top, left in DimResult struct
pResult->width = newWidth;
pResult->height = newHeight;
pResult->top = newTop;
pResult->left = newLeft;
return pNewImageData;
}
/* move frame down to the raw GIF API */
static cgif_result flushFrame(CGIF* pGIF, CGIF_Frame* pCur, CGIF_Frame* pBef) {
CGIFRaw_FrameConfig rawConfig;
DimResult dimResult;
uint8_t* pTmpImageData;
uint8_t* pBefImageData;
int isFirstFrame, useLCT, hasAlpha, hasSetTransp;
uint16_t numPaletteEntries;
uint16_t imageWidth, imageHeight, width, height, top, left;
uint8_t transIndex, disposalMethod;
cgif_result r;
imageWidth = pGIF->config.width;
imageHeight = pGIF->config.height;
isFirstFrame = (pBef == NULL) ? 1 : 0;
useLCT = (pCur->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) ? 1 : 0; // LCT stands for "local color table"
hasAlpha = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0;
hasSetTransp = (pCur->config.attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0;
disposalMethod = pCur->disposalMethod;
transIndex = pCur->transIndex;
// sanity check:
// at least one valid CT needed (global or local)
if(!useLCT && (pGIF->config.attrFlags & CGIF_ATTR_NO_GLOBAL_TABLE)) {
return CGIF_ERROR; // invalid config
}
// deactivate impossible size optimizations
// => in case alpha channel is used
// CGIF_FRAME_GEN_USE_TRANSPARENCY and CGIF_FRAME_GEN_USE_DIFF_WINDOW are not possible
if(isFirstFrame || hasAlpha) {
pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
}
// transparency setting (which areas are identical to the frame before) provided by user:
// CGIF_FRAME_GEN_USE_TRANSPARENCY not possible
if(hasSetTransp) {
pCur->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY);
}
numPaletteEntries = (useLCT) ? pCur->config.numLocalPaletteEntries : pGIF->config.numGlobalPaletteEntries;
// switch off transparency optimization if color table is full (no free spot for the transparent index), TBD: count used colors, adapt table
if(numPaletteEntries == 256) {
pCur->config.genFlags &= ~CGIF_FRAME_GEN_USE_TRANSPARENCY;
}
// purge overlap of current frame and frame before (width - height optim), if required (CGIF_FRAME_GEN_USE_DIFF_WINDOW set)
if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_DIFF_WINDOW) {
pTmpImageData = doWidthHeightOptim(pGIF, &pCur->config, &pBef->config, &dimResult);
width = dimResult.width;
height = dimResult.height;
top = dimResult.top;
left = dimResult.left;
} else {
pTmpImageData = NULL;
width = imageWidth;
height = imageHeight;
top = 0;
left = 0;
}
// mark matching areas of the previous frame as transparent, if required (CGIF_FRAME_GEN_USE_TRANSPARENCY set)
if(pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) {
// set transIndex to next free index
int pow2 = calcNextPower2Ex(numPaletteEntries);
pow2 = (pow2 < 2) ? 2 : pow2; // TBD keep transparency index behavior as in V0.1.0 (for now)
transIndex = (1 << pow2) - 1;
if(transIndex < numPaletteEntries) {
transIndex = (1 << (pow2 + 1)) - 1;
}
if(pTmpImageData == NULL) {
pTmpImageData = malloc(MULU16(imageWidth, imageHeight)); // TBD check return value of malloc
memcpy(pTmpImageData, pCur->config.pImageData, MULU16(imageWidth, imageHeight));
}
pBefImageData = pBef->config.pImageData;
for(int i = 0; i < height; ++i) {
for(int x = 0; x < width; ++x) {
if(cmpPixel(pGIF, &pCur->config, &pBef->config, pTmpImageData[MULU16(i, width) + x], pBefImageData[MULU16(top + i, imageWidth) + (left + x)]) == 0) {
pTmpImageData[MULU16(i, width) + x] = transIndex;
}
}
}
}
// move frame down to GIF raw API
rawConfig.pLCT = pCur->config.pLocalPalette;
rawConfig.pImageData = (pTmpImageData) ? pTmpImageData : pCur->config.pImageData;
rawConfig.attrFlags = 0;
if(hasAlpha || (pCur->config.genFlags & CGIF_FRAME_GEN_USE_TRANSPARENCY) || hasSetTransp) {
rawConfig.attrFlags |= CGIF_RAW_FRAME_ATTR_HAS_TRANS;
}
rawConfig.attrFlags |= (pCur->config.attrFlags & CGIF_FRAME_ATTR_INTERLACED) ? CGIF_RAW_FRAME_ATTR_INTERLACED : 0;
rawConfig.width = width;
rawConfig.height = height;
rawConfig.top = top;
rawConfig.left = left;
rawConfig.delay = pCur->config.delay;
rawConfig.sizeLCT = (useLCT) ? pCur->config.numLocalPaletteEntries : 0;
rawConfig.disposalMethod = disposalMethod;
rawConfig.transIndex = transIndex;
r = cgif_raw_addframe(pGIF->pGIFRaw, &rawConfig);
free(pTmpImageData);
return r;
}
static void freeFrame(CGIF_Frame* pFrame) {
if(pFrame) {
free(pFrame->config.pImageData);
if(pFrame->config.attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
free(pFrame->config.pLocalPalette);
}
free(pFrame);
}
}
static void copyFrameConfig(CGIF_FrameConfig* pDest, CGIF_FrameConfig* pSrc) {
pDest->pLocalPalette = pSrc->pLocalPalette; // might need a deep copy
pDest->pImageData = pSrc->pImageData; // might need a deep copy
pDest->attrFlags = pSrc->attrFlags;
pDest->genFlags = pSrc->genFlags;
pDest->delay = pSrc->delay;
pDest->numLocalPaletteEntries = pSrc->numLocalPaletteEntries;
// copy transIndex if necessary (field added with V0.2.0; avoid binary incompatibility)
if(pSrc->attrFlags & (CGIF_FRAME_ATTR_HAS_ALPHA | CGIF_FRAME_ATTR_HAS_SET_TRANS)) {
pDest->transIndex = pSrc->transIndex;
}
}
/* queue a new GIF frame */
int cgif_addframe(CGIF* pGIF, CGIF_FrameConfig* pConfig) {
CGIF_Frame* pNewFrame;
int hasAlpha, hasSetTransp;
int i;
cgif_result r;
// check for previous errors
if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
return pGIF->curResult;
}
hasAlpha = ((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) || (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) ? 1 : 0; // alpha channel is present
hasSetTransp = (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_SET_TRANS) ? 1 : 0; // user provided transparency setting (identical areas marked by user)
// check for invalid configs:
// cannot set alpha channel and user-provided transparency at the same time.
if(hasAlpha && hasSetTransp) {
pGIF->curResult = CGIF_ERROR;
return pGIF->curResult;
}
// cannot set global and local alpha channel at the same time
if((pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) && (pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA)) {
pGIF->curResult = CGIF_ERROR;
return pGIF->curResult;
}
// search for free slot in frame queue
for(i = 1; i < SIZE_FRAME_QUEUE && pGIF->aFrames[i] != NULL; ++i);
// check whether the queue is full
// when queue is full: we need to flush one frame.
if(i == SIZE_FRAME_QUEUE) {
r = flushFrame(pGIF, pGIF->aFrames[1], pGIF->aFrames[0]);
freeFrame(pGIF->aFrames[0]);
pGIF->aFrames[0] = NULL;
// check for errors
if(r != CGIF_OK) {
pGIF->curResult = r;
return pGIF->curResult;
}
i = SIZE_FRAME_QUEUE - 1;
// keep the flushed frame in memory, as we might need it to write the next one.
pGIF->aFrames[0] = pGIF->aFrames[1];
pGIF->aFrames[1] = pGIF->aFrames[2];
}
// create new Frame struct + make a deep copy of pConfig.
pNewFrame = malloc(sizeof(CGIF_Frame));
copyFrameConfig(&(pNewFrame->config), pConfig);
pNewFrame->config.pImageData = malloc(MULU16(pGIF->config.width, pGIF->config.height));
memcpy(pNewFrame->config.pImageData, pConfig->pImageData, MULU16(pGIF->config.width, pGIF->config.height));
// make a deep copy of the local color table, if required.
if(pConfig->attrFlags & CGIF_FRAME_ATTR_USE_LOCAL_TABLE) {
pNewFrame->config.pLocalPalette = malloc(pConfig->numLocalPaletteEntries * 3);
memcpy(pNewFrame->config.pLocalPalette, pConfig->pLocalPalette, pConfig->numLocalPaletteEntries * 3);
}
pNewFrame->disposalMethod = DISPOSAL_METHOD_LEAVE;
pNewFrame->transIndex = 0;
pGIF->aFrames[i] = pNewFrame; // add frame to queue
// check whether we need to adapt the disposal method of the frame before.
if(pGIF->config.attrFlags & CGIF_ATTR_HAS_TRANSPARENCY) {
pGIF->aFrames[i]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // TBD might be removed
pGIF->aFrames[i]->transIndex = 0;
if(pGIF->aFrames[i - 1] != NULL) {
pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_TRANSPARENCY | CGIF_FRAME_GEN_USE_DIFF_WINDOW);
pGIF->aFrames[i - 1]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // restore to background color
}
}
// set per-frame alpha channel (we need to adapt the disposal method of the frame before)
if(pConfig->attrFlags & CGIF_FRAME_ATTR_HAS_ALPHA) {
pGIF->aFrames[i]->transIndex = pConfig->transIndex;
if(pGIF->aFrames[i - 1] != NULL) {
pGIF->aFrames[i - 1]->config.genFlags &= ~(CGIF_FRAME_GEN_USE_DIFF_WINDOW); // width/height optim not possible for frame before
pGIF->aFrames[i - 1]->disposalMethod = DISPOSAL_METHOD_BACKGROUND; // restore to background color
}
}
// user provided transparency setting
if(hasSetTransp) {
pGIF->aFrames[i]->transIndex = pConfig->transIndex;
}
pGIF->curResult = CGIF_OK;
return pGIF->curResult;
}
/* close the GIF-file and free allocated space */
int cgif_close(CGIF* pGIF) {
int r;
cgif_result result;
// check for previous errors
if(pGIF->curResult != CGIF_OK) {
goto CGIF_CLOSE_Cleanup;
}
// flush all remaining frames in queue
for(int i = 1; i < SIZE_FRAME_QUEUE; ++i) {
if(pGIF->aFrames[i] != NULL) {
r = flushFrame(pGIF, pGIF->aFrames[i], pGIF->aFrames[i - 1]);
if(r != CGIF_OK) {
pGIF->curResult = r;
break;
}
}
}
// cleanup
CGIF_CLOSE_Cleanup:
r = cgif_raw_close(pGIF->pGIFRaw); // close raw GIF stream
// check for errors
if(r != CGIF_OK) {
pGIF->curResult = r;
}
if(pGIF->pFile) {
r = fclose(pGIF->pFile); // we are done at this point => close the file
if(r) {
pGIF->curResult = CGIF_ECLOSE; // error: fclose failed
}
}
for(int i = 0; i < 3; ++i) {
freeFrame(pGIF->aFrames[i]);
}
result = pGIF->curResult;
freeCGIF(pGIF);
// catch internal value CGIF_PENDING
if(result == CGIF_PENDING) {
result = CGIF_ERROR;
}
return result; // return previous result
}

View File

@ -0,0 +1,622 @@
#include <stdlib.h>
#include <string.h>
#include "cgif_raw.h"
#define SIZE_MAIN_HEADER (13)
#define SIZE_APP_EXT (19)
#define SIZE_FRAME_HEADER (10)
#define SIZE_GRAPHIC_EXT ( 8)
#define HEADER_OFFSET_SIGNATURE (0x00)
#define HEADER_OFFSET_VERSION (0x03)
#define HEADER_OFFSET_WIDTH (0x06)
#define HEADER_OFFSET_HEIGHT (0x08)
#define HEADER_OFFSET_PACKED_FIELD (0x0A)
#define HEADER_OFFSET_BACKGROUND (0x0B)
#define HEADER_OFFSET_MAP (0x0C)
#define IMAGE_OFFSET_LEFT (0x01)
#define IMAGE_OFFSET_TOP (0x03)
#define IMAGE_OFFSET_WIDTH (0x05)
#define IMAGE_OFFSET_HEIGHT (0x07)
#define IMAGE_OFFSET_PACKED_FIELD (0x09)
#define IMAGE_PACKED_FIELD(a) (*((uint8_t*) (a + IMAGE_OFFSET_PACKED_FIELD)))
#define APPEXT_OFFSET_NAME (0x03)
#define APPEXT_NETSCAPE_OFFSET_LOOPS (APPEXT_OFFSET_NAME + 13)
#define GEXT_OFFSET_DELAY (0x04)
#define MAX_CODE_LEN 12 // maximum code length for lzw
#define MAX_DICT_LEN (1uL << MAX_CODE_LEN) // maximum length of the dictionary
#define BLOCK_SIZE 0xFF // number of bytes in one block of the image data
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
typedef struct {
uint8_t* pRasterData;
uint32_t sizeRasterData;
} LZWResult;
typedef struct {
uint16_t* pTreeInit; // LZW dictionary tree for the initial dictionary (0-255 max)
uint16_t* pTreeList; // LZW dictionary tree as list (max. number of children per node = 1)
uint16_t* pTreeMap; // LZW dictionary tree as map (backup to pTreeList in case more than 1 child is present)
uint16_t* pLZWData; // pointer to LZW data
const uint8_t* pImageData; // pointer to image data
uint32_t numPixel; // number of pixels per frame
uint32_t LZWPos; // position of the current LZW code
uint16_t dictPos; // currrent position in dictionary, we need to store 0-4096 -- so there are at least 13 bits needed here
uint16_t mapPos; // current position in LZW tree mapping table
} LZWGenState;
/* converts host U16 to little-endian (LE) U16 */
static uint16_t hU16toLE(const uint16_t n) {
int isBE;
uint16_t newVal;
uint16_t one;
one = 1;
isBE = *((uint8_t*)&one) ? 0 : 1;
if(isBE) {
newVal = (n >> 8) | (n << 8);
} else {
newVal = n; // already LE
}
return newVal;
}
/* calculate next power of two exponent of given number (n MUST be <= 256) */
static uint8_t calcNextPower2Ex(uint16_t n) {
uint8_t nextPow2;
for (nextPow2 = 0; n > (1uL << nextPow2); ++nextPow2);
return nextPow2;
}
/* compute which initial LZW-code length is needed */
static uint8_t calcInitCodeLen(uint16_t numEntries) {
uint8_t index;
index = calcNextPower2Ex(numEntries);
return (index < 3) ? 3 : index + 1;
}
/* reset the dictionary of known LZW codes -- will reset the current code length as well */
static void resetDict(LZWGenState* pContext, const uint16_t initDictLen) {
pContext->dictPos = initDictLen + 2; // reset current position in dictionary (number of colors + 2 for start and end code)
pContext->mapPos = 1;
pContext->pLZWData[pContext->LZWPos] = initDictLen; // issue clear-code
++(pContext->LZWPos); // increment position in LZW data
// reset LZW list
memset(pContext->pTreeInit, 0, initDictLen * sizeof(uint16_t) * initDictLen);
memset(pContext->pTreeList, 0, ((sizeof(uint16_t) * 2) + sizeof(uint16_t)) * MAX_DICT_LEN);
}
/* add new child node */
static void add_child(LZWGenState* pContext, const uint16_t parentIndex, const uint16_t LZWIndex, const uint16_t initDictLen, const uint8_t nextColor) {
uint16_t* pTreeList;
uint16_t mapPos;
pTreeList = pContext->pTreeList;
mapPos = pTreeList[parentIndex * (2 + 1)];
if(!mapPos) { // if pTreeMap is not used yet for the parent node
if(pTreeList[parentIndex * (2 + 1) + 2]) { // if at least one child node exists, switch to pTreeMap
mapPos = pContext->mapPos;
// add child to mapping table (pTreeMap)
memset(pContext->pTreeMap + ((mapPos - 1) * initDictLen), 0, initDictLen * sizeof(uint16_t));
pContext->pTreeMap[(mapPos - 1) * initDictLen + nextColor] = LZWIndex;
pTreeList[parentIndex * (2 + 1)] = mapPos;
++(pContext->mapPos);
} else { // use the free spot in pTreeList for the child node
pTreeList[parentIndex * (2 + 1) + 1] = nextColor; // color that leads to child node
pTreeList[parentIndex * (2 + 1) + 2] = LZWIndex; // position of child node
}
} else { // directly add child node to pTreeMap
pContext->pTreeMap[(mapPos - 1) * initDictLen + nextColor] = LZWIndex;
}
++(pContext->dictPos); // increase current position in the dictionary
}
/* find next LZW code representing the longest pixel sequence that is still in the dictionary*/
static int lzw_crawl_tree(LZWGenState* pContext, uint32_t* pStrPos, uint16_t parentIndex, const uint16_t initDictLen) {
uint16_t* pTreeInit;
uint16_t* pTreeList;
uint32_t strPos;
uint16_t nextParent;
uint16_t mapPos;
if(parentIndex >= initDictLen) {
return CGIF_EINDEX; // error: index in image data out-of-bounds
}
pTreeInit = pContext->pTreeInit;
pTreeList = pContext->pTreeList;
strPos = *pStrPos;
// get the next LZW code from pTreeInit:
// the initial nodes (0-255 max) have more children on average.
// use the mapping approach right from the start for these nodes.
if(strPos < (pContext->numPixel - 1)) {
if(pContext->pImageData[strPos + 1] >= initDictLen) {
return CGIF_EINDEX; // error: index in image data out-of-bounds
}
nextParent = pTreeInit[parentIndex * initDictLen + pContext->pImageData[strPos + 1]];
if(nextParent) {
parentIndex = nextParent;
++strPos;
} else {
pContext->pLZWData[pContext->LZWPos] = parentIndex; // write last LZW code in LZW data
++(pContext->LZWPos);
if(pContext->dictPos < MAX_DICT_LEN) {
pTreeInit[parentIndex * initDictLen + pContext->pImageData[strPos + 1]] = pContext->dictPos;
++(pContext->dictPos);
} else {
resetDict(pContext, initDictLen);
}
++strPos;
*pStrPos = strPos;
return CGIF_OK;
}
}
// inner loop for codes > initDictLen
while(strPos < (pContext->numPixel - 1)) {
if(pContext->pImageData[strPos + 1] >= initDictLen) {
return CGIF_EINDEX; // error: index in image data out-of-bounds
}
// first try to find child in LZW list
if(pTreeList[parentIndex * (2 + 1) + 2] && pTreeList[parentIndex * (2 + 1) + 1] == pContext->pImageData[strPos + 1]) {
parentIndex = pTreeList[parentIndex * (2 + 1) + 2];
++strPos;
continue;
}
// not found child yet? try to look into the LZW mapping table
mapPos = pContext->pTreeList[parentIndex * (2 + 1)];
if(mapPos) {
nextParent = pContext->pTreeMap[(mapPos - 1) * initDictLen + pContext->pImageData[strPos + 1]];
if(nextParent) {
parentIndex = nextParent;
++strPos;
continue;
}
}
// still not found child? add current parentIndex to LZW data and add new child
pContext->pLZWData[pContext->LZWPos] = parentIndex; // write last LZW code in LZW data
++(pContext->LZWPos);
if(pContext->dictPos < MAX_DICT_LEN) { // if LZW-dictionary is not full yet
add_child(pContext, parentIndex, pContext->dictPos, initDictLen, pContext->pImageData[strPos + 1]); // add new LZW code to dictionary
} else {
// the dictionary reached its maximum code => reset it (not required by GIF-standard but mostly done like this)
resetDict(pContext, initDictLen);
}
++strPos;
*pStrPos = strPos;
return CGIF_OK;
}
pContext->pLZWData[pContext->LZWPos] = parentIndex; // if the end of the image is reached, write last LZW code
++(pContext->LZWPos);
++strPos;
*pStrPos = strPos;
return CGIF_OK;
}
/* generate LZW-codes that compress the image data*/
static int lzw_generate(LZWGenState* pContext, uint16_t initDictLen) {
uint32_t strPos;
int r;
uint8_t parentIndex;
strPos = 0; // start at beginning of the image data
resetDict(pContext, initDictLen); // reset dictionary and issue clear-code at first
while(strPos < pContext->numPixel) { // while there are still image data to be encoded
parentIndex = pContext->pImageData[strPos]; // start at root node
// get longest sequence that is still in dictionary, return new position in image data
r = lzw_crawl_tree(pContext, &strPos, (uint16_t)parentIndex, initDictLen);
if(r != CGIF_OK) {
return r; // error: return error code to callee
}
}
pContext->pLZWData[pContext->LZWPos] = initDictLen + 1; // termination code
++(pContext->LZWPos);
return CGIF_OK;
}
/* pack the LZW data into a byte sequence*/
static uint32_t create_byte_list(uint8_t *byteList, uint32_t lzwPos, uint16_t *lzwStr, uint16_t initDictLen, uint8_t initCodeLen){
uint32_t i;
uint32_t dictPos; // counting new LZW codes
uint16_t n = 2 * initDictLen; // if n - initDictLen == dictPos, the LZW code size is incremented by 1 bit
uint32_t bytePos = 0; // position of current byte
uint8_t bitOffset = 0; // number of bits used in the last byte
uint8_t lzwCodeLen = initCodeLen; // dynamically increasing length of the LZW codes
int correctLater = 0; // 1: one empty byte too much if end is reached after current code, 0 otherwise
byteList[0] = 0; // except from the 1st byte all other bytes should be initialized stepwise (below)
// the very first symbol might be the clear-code. However, this is not mandatory. Quote:
// "Encoders should output a Clear code as the first code of each image data stream."
// We keep the option to NOT output the clear code as the first symbol in this function.
dictPos = 1;
for(i = 0; i < lzwPos; ++i) { // loop over all LZW codes
if((lzwCodeLen < MAX_CODE_LEN) && ((uint32_t)(n - (initDictLen)) == dictPos)) { // larger code is used for the 1st time at i = 256 ...+ 512 ...+ 1024 -> 256, 768, 1792
++lzwCodeLen; // increment the length of the LZW codes (bit units)
n *= 2; // set threshold for next increment of LZW code size
}
correctLater = 0; // 1 indicates that one empty byte is too much at the end
byteList[bytePos] |= ((uint8_t)(lzwStr[i] << bitOffset)); // add 1st bits of the new LZW code to the byte containing part of the previous code
if(lzwCodeLen + bitOffset >= 8) { // if the current byte is not enough of the LZW code
if(lzwCodeLen + bitOffset == 8) { // if just this byte is filled exactly
byteList[++bytePos] = 0; // byte is full -- go to next byte and initialize as 0
correctLater = 1; // use if one 0byte to much at the end
} else if(lzwCodeLen + bitOffset < 16) { // if the next byte is not completely filled
byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset));
} else if(lzwCodeLen + bitOffset == 16) { // if the next byte is exactly filled by LZW code
byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset));
byteList[++bytePos] = 0; // byte is full -- go to next byte and initialize as 0
correctLater = 1; // use if one 0byte to much at the end
} else { // lzw-code ranges over 3 bytes in total
byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (8-bitOffset)); // write part of LZW code to next byte
byteList[++bytePos] = (uint8_t)(lzwStr[i] >> (16-bitOffset)); // write part of LZW code to byte after next byte
}
}
bitOffset = (lzwCodeLen + bitOffset) % 8; // how many bits of the last byte are used?
++dictPos; // increment count of LZW codes
if(lzwStr[i] == initDictLen) { // if a clear code appears in the LZW data
lzwCodeLen = initCodeLen; // reset length of LZW codes
n = 2 * initDictLen; // reset threshold for next increment of LZW code length
dictPos = 1; // reset (see comment below)
// take first code already into account to increment lzwCodeLen exactly when the code length cannot represent the current maximum symbol.
// Note: This is usually done implicitly, as the very first symbol is a clear-code itself.
}
}
// comment: the last byte can be zero in the following case only:
// terminate code has been written (initial dict length + 1), but current code size is larger so padding zero bits were added and extend into the next byte(s).
if(correctLater) { // if an unneccessaray empty 0-byte was initialized at the end
--bytePos; // don't consider the last empty byte
}
return bytePos;
}
/* put byte sequence in blocks as required by GIF-format */
static uint32_t create_byte_list_block(uint8_t *byteList, uint8_t *byteListBlock, const uint32_t numBytes) {
uint32_t i;
uint32_t numBlock = numBytes / BLOCK_SIZE; // number of byte blocks with length BLOCK_SIZE
uint8_t numRest = numBytes % BLOCK_SIZE; // number of bytes in last block (if not completely full)
for(i = 0; i < numBlock; ++i) { // loop over all blocks
byteListBlock[i * (BLOCK_SIZE+1)] = BLOCK_SIZE; // number of bytes in the following block
memcpy(byteListBlock + 1+i*(BLOCK_SIZE+1), byteList + i*BLOCK_SIZE, BLOCK_SIZE); // copy block from byteList to byteListBlock
}
if(numRest>0) {
byteListBlock[numBlock*(BLOCK_SIZE+1)] = numRest; // number of bytes in the following block
memcpy(byteListBlock + 1+numBlock*(BLOCK_SIZE+1), byteList + numBlock*BLOCK_SIZE, numRest); // copy block from byteList to byteListBlock
byteListBlock[1 + numBlock * (BLOCK_SIZE + 1) + numRest] = 0; // set 0 at end of frame
return 1 + numBlock * (BLOCK_SIZE + 1) + numRest; // index of last entry in byteListBlock
}
// all LZW blocks in the frame have the same block size (BLOCK_SIZE), so there are no remaining bytes to be writen.
byteListBlock[numBlock *(BLOCK_SIZE + 1)] = 0; // set 0 at end of frame
return numBlock *(BLOCK_SIZE + 1); // index of last entry in byteListBlock
}
/* create all LZW raster data in GIF-format */
static int LZW_GenerateStream(LZWResult* pResult, const uint32_t numPixel, const uint8_t* pImageData, const uint16_t initDictLen, const uint8_t initCodeLen){
LZWGenState* pContext;
uint32_t lzwPos, bytePos;
uint32_t bytePosBlock;
int r;
// TBD recycle LZW tree list and map (if possible) to decrease the number of allocs
pContext = malloc(sizeof(LZWGenState)); // TBD check return value of malloc
pContext->pTreeInit = malloc((initDictLen * sizeof(uint16_t)) * initDictLen); // TBD check return value of malloc
pContext->pTreeList = malloc(((sizeof(uint16_t) * 2) + sizeof(uint16_t)) * MAX_DICT_LEN); // TBD check return value of malloc TBD check size
pContext->pTreeMap = malloc(((MAX_DICT_LEN / 2) + 1) * (initDictLen * sizeof(uint16_t))); // TBD check return value of malloc
pContext->numPixel = numPixel;
pContext->pImageData = pImageData;
pContext->pLZWData = malloc(sizeof(uint16_t) * (numPixel + 2)); // TBD check return value of malloc
pContext->LZWPos = 0;
// actually generate the LZW sequence.
r = lzw_generate(pContext, initDictLen);
if(r != CGIF_OK) {
goto LZWGENERATE_Cleanup;
}
lzwPos = pContext->LZWPos;
// pack the generated LZW data into blocks of 255 bytes
uint8_t *byteList; // lzw-data packed in byte-list
uint8_t *byteListBlock; // lzw-data packed in byte-list with 255-block structure
uint64_t MaxByteListLen = MAX_CODE_LEN*lzwPos/8ul +2ul +1ul; // conservative upper bound
uint64_t MaxByteListBlockLen = MAX_CODE_LEN*lzwPos*(BLOCK_SIZE+1ul)/8ul/BLOCK_SIZE +2ul +1ul +1ul; // conservative upper bound
byteList = malloc(MaxByteListLen); // TBD check return value of malloc
byteListBlock = malloc(MaxByteListBlockLen); // TBD check return value of malloc
bytePos = create_byte_list(byteList,lzwPos, pContext->pLZWData, initDictLen, initCodeLen);
bytePosBlock = create_byte_list_block(byteList, byteListBlock, bytePos+1);
free(byteList);
pResult->sizeRasterData = bytePosBlock + 1; // save
pResult->pRasterData = byteListBlock;
LZWGENERATE_Cleanup:
free(pContext->pLZWData);
free(pContext->pTreeInit);
free(pContext->pTreeList);
free(pContext->pTreeMap);
free(pContext);
return r;
}
/* initialize the header of the GIF */
static void initMainHeader(const CGIFRaw_Config* pConfig, uint8_t* pHeader) {
uint16_t width, height;
uint8_t pow2GlobalPalette;
width = pConfig->width;
height = pConfig->height;
// set header to a clean state
memset(pHeader, 0, SIZE_MAIN_HEADER);
// set Signature field to value "GIF"
pHeader[HEADER_OFFSET_SIGNATURE] = 'G';
pHeader[HEADER_OFFSET_SIGNATURE + 1] = 'I';
pHeader[HEADER_OFFSET_SIGNATURE + 2] = 'F';
// set Version field to value "89a"
pHeader[HEADER_OFFSET_VERSION] = '8';
pHeader[HEADER_OFFSET_VERSION + 1] = '9';
pHeader[HEADER_OFFSET_VERSION + 2] = 'a';
// set width of screen (LE ordering)
const uint16_t widthLE = hU16toLE(width);
memcpy(pHeader + HEADER_OFFSET_WIDTH, &widthLE, sizeof(uint16_t));
// set height of screen (LE ordering)
const uint16_t heightLE = hU16toLE(height);
memcpy(pHeader + HEADER_OFFSET_HEIGHT, &heightLE, sizeof(uint16_t));
// init packed field
if(pConfig->sizeGCT) {
pHeader[HEADER_OFFSET_PACKED_FIELD] = (1 << 7); // M = 1 (see GIF specc): global color table is present
// calculate needed size of global color table (GCT).
// MUST be a power of two.
pow2GlobalPalette = calcNextPower2Ex(pConfig->sizeGCT);
pow2GlobalPalette = (pow2GlobalPalette < 1) ? 1 : pow2GlobalPalette; // minimum size is 2^1
pHeader[HEADER_OFFSET_PACKED_FIELD] |= ((pow2GlobalPalette - 1) << 0); // set size of GCT (0 - 7 in header + 1)
}
}
/* initialize NETSCAPE app extension block (needed for animation) */
static void initAppExtBlock(uint8_t* pAppExt, uint16_t numLoops) {
memset(pAppExt, 0, SIZE_APP_EXT);
// set data
pAppExt[0] = 0x21;
pAppExt[1] = 0xFF; // start of block
pAppExt[2] = 0x0B; // eleven bytes to follow
// write identifier for Netscape animation extension
pAppExt[APPEXT_OFFSET_NAME] = 'N';
pAppExt[APPEXT_OFFSET_NAME + 1] = 'E';
pAppExt[APPEXT_OFFSET_NAME + 2] = 'T';
pAppExt[APPEXT_OFFSET_NAME + 3] = 'S';
pAppExt[APPEXT_OFFSET_NAME + 4] = 'C';
pAppExt[APPEXT_OFFSET_NAME + 5] = 'A';
pAppExt[APPEXT_OFFSET_NAME + 6] = 'P';
pAppExt[APPEXT_OFFSET_NAME + 7] = 'E';
pAppExt[APPEXT_OFFSET_NAME + 8] = '2';
pAppExt[APPEXT_OFFSET_NAME + 9] = '.';
pAppExt[APPEXT_OFFSET_NAME + 10] = '0';
pAppExt[APPEXT_OFFSET_NAME + 11] = 0x03; // 3 bytes to follow
pAppExt[APPEXT_OFFSET_NAME + 12] = 0x01; // TBD clarify
// set number of repetitions (animation; LE ordering)
const uint16_t netscapeLE = hU16toLE(numLoops);
memcpy(pAppExt + APPEXT_NETSCAPE_OFFSET_LOOPS, &netscapeLE, sizeof(uint16_t));
}
/* write numBytes dummy bytes */
static int writeDummyBytes(cgif_write_fn* pWriteFn, void* pContext, int numBytes) {
int rWrite = 0;
const uint8_t dummyByte = 0;
for(int i = 0; i < numBytes; ++i) {
rWrite |= pWriteFn(pContext, &dummyByte, 1);
}
return rWrite;
}
CGIFRaw* cgif_raw_newgif(const CGIFRaw_Config* pConfig) {
uint8_t aAppExt[SIZE_APP_EXT];
uint8_t aHeader[SIZE_MAIN_HEADER];
CGIFRaw* pGIF;
int rWrite;
// check for invalid GCT size
if(pConfig->sizeGCT > 256) {
return NULL; // invalid GCT size
}
pGIF = malloc(sizeof(CGIFRaw));
if(!pGIF) {
return NULL;
}
memcpy(&(pGIF->config), pConfig, sizeof(CGIFRaw_Config));
// initiate all sections we can at this stage:
// - main GIF header
// - global color table (GCT), if required
// - netscape application extension (for animation), if required
initMainHeader(pConfig, aHeader);
rWrite = pConfig->pWriteFn(pConfig->pContext, aHeader, SIZE_MAIN_HEADER);
// GCT required? => write it.
if(pConfig->sizeGCT) {
rWrite |= pConfig->pWriteFn(pConfig->pContext, pConfig->pGCT, pConfig->sizeGCT * 3);
uint8_t pow2GCT = calcNextPower2Ex(pConfig->sizeGCT);
pow2GCT = (pow2GCT < 1) ? 1 : pow2GCT; // minimum size is 2^1
const uint16_t numBytesLeft = ((1 << pow2GCT) - pConfig->sizeGCT) * 3;
rWrite |= writeDummyBytes(pConfig->pWriteFn, pConfig->pContext, numBytesLeft);
}
// GIF should be animated? => init & write app extension header ("NETSCAPE2.0")
// No loop? Don't write NETSCAPE extension.
if((pConfig->attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) && !(pConfig->attrFlags & CGIF_RAW_ATTR_NO_LOOP)) {
initAppExtBlock(aAppExt, pConfig->numLoops);
rWrite |= pConfig->pWriteFn(pConfig->pContext, aAppExt, SIZE_APP_EXT);
}
// check for write errors
if(rWrite) {
free(pGIF);
return NULL;
}
// assume error per default.
// set to CGIF_OK by the first successful cgif_raw_addframe() call, as a GIF without frames is invalid.
pGIF->curResult = CGIF_PENDING;
return pGIF;
}
/* add new frame to the raw GIF stream */
cgif_result cgif_raw_addframe(CGIFRaw* pGIF, const CGIFRaw_FrameConfig* pConfig) {
uint8_t aFrameHeader[SIZE_FRAME_HEADER];
uint8_t aGraphicExt[SIZE_GRAPHIC_EXT];
LZWResult encResult;
int r, rWrite;
const int useLCT = pConfig->sizeLCT; // LCT stands for "local color table"
const int isInterlaced = (pConfig->attrFlags & CGIF_RAW_FRAME_ATTR_INTERLACED) ? 1 : 0;
uint16_t numEffColors; // number of effective colors
uint16_t initDictLen;
uint8_t pow2LCT, initCodeLen;
if(pGIF->curResult != CGIF_OK && pGIF->curResult != CGIF_PENDING) {
return pGIF->curResult; // return previous error
}
// check for invalid LCT size
if(pConfig->sizeLCT > 256) {
pGIF->curResult = CGIF_ERROR; // invalid LCT size
return pGIF->curResult;
}
rWrite = 0;
// set frame header to a clean state
memset(aFrameHeader, 0, SIZE_FRAME_HEADER);
// set needed fields in frame header
aFrameHeader[0] = ','; // set frame seperator
if(useLCT) {
pow2LCT = calcNextPower2Ex(pConfig->sizeLCT);
pow2LCT = (pow2LCT < 1) ? 1 : pow2LCT; // minimum size is 2^1
IMAGE_PACKED_FIELD(aFrameHeader) = (1 << 7);
// set size of local color table (0-7 in header + 1)
IMAGE_PACKED_FIELD(aFrameHeader) |= ((pow2LCT- 1) << 0);
numEffColors = pConfig->sizeLCT;
} else {
numEffColors = pGIF->config.sizeGCT; // global color table in use
}
// encode frame interlaced?
IMAGE_PACKED_FIELD(aFrameHeader) |= (isInterlaced << 6);
// transparency in use? we might need to increase numEffColors
if((pGIF->config.attrFlags & (CGIF_RAW_ATTR_IS_ANIMATED)) && (pConfig->attrFlags & (CGIF_RAW_FRAME_ATTR_HAS_TRANS)) && pConfig->transIndex >= numEffColors) {
numEffColors = pConfig->transIndex + 1;
}
// calculate initial code length and initial dict length
initCodeLen = calcInitCodeLen(numEffColors);
initDictLen = 1uL << (initCodeLen - 1);
const uint16_t frameWidthLE = hU16toLE(pConfig->width);
const uint16_t frameHeightLE = hU16toLE(pConfig->height);
const uint16_t frameTopLE = hU16toLE(pConfig->top);
const uint16_t frameLeftLE = hU16toLE(pConfig->left);
memcpy(aFrameHeader + IMAGE_OFFSET_WIDTH, &frameWidthLE, sizeof(uint16_t));
memcpy(aFrameHeader + IMAGE_OFFSET_HEIGHT, &frameHeightLE, sizeof(uint16_t));
memcpy(aFrameHeader + IMAGE_OFFSET_TOP, &frameTopLE, sizeof(uint16_t));
memcpy(aFrameHeader + IMAGE_OFFSET_LEFT, &frameLeftLE, sizeof(uint16_t));
// apply interlaced pattern
// TBD creating a copy of pImageData is not ideal, but changes on the LZW encoding would
// be necessary otherwise.
if(isInterlaced) {
uint8_t* pInterlaced = malloc(MULU16(pConfig->width, pConfig->height));
if(pInterlaced == NULL) {
pGIF->curResult = CGIF_EALLOC;
return pGIF->curResult;
}
uint8_t* p = pInterlaced;
// every 8th row (starting with row 0)
for(uint32_t i = 0; i < pConfig->height; i += 8) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 8th row (starting with row 4)
for(uint32_t i = 4; i < pConfig->height; i += 8) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 4th row (starting with row 2)
for(uint32_t i = 2; i < pConfig->height; i += 4) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
// every 2th row (starting with row 1)
for(uint32_t i = 1; i < pConfig->height; i += 2) {
memcpy(p, pConfig->pImageData + i * pConfig->width, pConfig->width);
p += pConfig->width;
}
r = LZW_GenerateStream(&encResult, MULU16(pConfig->width, pConfig->height), pInterlaced, initDictLen, initCodeLen);
free(pInterlaced);
} else {
r = LZW_GenerateStream(&encResult, MULU16(pConfig->width, pConfig->height), pConfig->pImageData, initDictLen, initCodeLen);
}
// generate LZW raster data (actual image data)
// check for errors
if(r != CGIF_OK) {
pGIF->curResult = r;
return r;
}
// do things for animation / transparency, if required.
if(pGIF->config.attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) {
memset(aGraphicExt, 0, SIZE_GRAPHIC_EXT);
aGraphicExt[0] = 0x21;
aGraphicExt[1] = 0xF9;
aGraphicExt[2] = 0x04;
aGraphicExt[3] = pConfig->disposalMethod;
// set flag indicating that transparency is used, if required.
if(pConfig->attrFlags & CGIF_RAW_FRAME_ATTR_HAS_TRANS) {
aGraphicExt[3] |= 0x01;
aGraphicExt[6] = pConfig->transIndex;
}
// set delay (LE ordering)
const uint16_t delayLE = hU16toLE(pConfig->delay);
memcpy(aGraphicExt + GEXT_OFFSET_DELAY, &delayLE, sizeof(uint16_t));
}
// write frame
const uint8_t initialCodeSize = initCodeLen - 1;
if(pGIF->config.attrFlags & CGIF_RAW_ATTR_IS_ANIMATED) {
rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, aGraphicExt, SIZE_GRAPHIC_EXT);
}
rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, aFrameHeader, SIZE_FRAME_HEADER);
if(useLCT) {
rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, pConfig->pLCT, pConfig->sizeLCT * 3);
const uint16_t numBytesLeft = ((1 << pow2LCT) - pConfig->sizeLCT) * 3;
rWrite |= writeDummyBytes(pGIF->config.pWriteFn, pGIF->config.pContext, numBytesLeft);
}
rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, &initialCodeSize, 1);
rWrite |= pGIF->config.pWriteFn(pGIF->config.pContext, encResult.pRasterData, encResult.sizeRasterData);
// check for write errors
if(rWrite) {
pGIF->curResult = CGIF_EWRITE;
} else {
pGIF->curResult = CGIF_OK;
}
// cleanup
free(encResult.pRasterData);
return pGIF->curResult;
}
cgif_result cgif_raw_close(CGIFRaw* pGIF) {
int rWrite;
cgif_result result;
rWrite = pGIF->config.pWriteFn(pGIF->config.pContext, (unsigned char*) ";", 1); // write term symbol
// check for write errors
if(rWrite) {
pGIF->curResult = CGIF_EWRITE;
}
result = pGIF->curResult;
free(pGIF);
return result;
}

View File

@ -0,0 +1,541 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "cgif.h"
#define MULU16(a, b) (((uint32_t)a) * ((uint32_t)b)) // helper macro to correctly multiply two U16's without default signed int promotion
#define MAX(a,b) (a >= b ? a : b)
#define MIN(a,b) (a <= b ? a : b)
#define MAX_TABLE_SIZE 16777216 // 256*256*256
#define MAX_COLLISIONS 30 // maximum number of collisions before hash-table resize
struct st_cgif_rgb {
CGIF* pGIF;
CGIFrgb_Config config;
uint8_t* pBefImageData;
cgif_chan_fmt befFmtChan;
};
typedef struct {
uint8_t r,g,b,a;
} Pixel;
typedef struct treeNode {
struct treeNode* child0; // pointer to child-node (elements smaller or equal than mean)
struct treeNode* child1; // pointer to child-node (elements larger than mean)
float mean[3]; // average of the colors
uint32_t idxMin, idxMax; // minimum and maximum index referring to global palette
uint8_t cutDim; // dimension along which the cut (node split) is done
uint8_t colIdx; // color index (only meaningful for leave node)
bool isLeave; // is leave node or not
} treeNode;
static uint64_t argmax64(float* arry, uint64_t n){
uint64_t imax = 0;
float vmax = 0;
for(uint64_t i = 0; i < n; ++i) {
if(arry[i] > vmax){
imax = i;
vmax = arry[i];
}
}
return imax;
}
/* get the next prime number above the next power of two */
static int getNextPrimePower2(int N){
if(N <= 512) {return 521;}
else if(N <= 1024) {return 1031;}
else if (N <= 2048) {return 2053;}
else if (N <= 4096) {return 4099;}
else if (N <= 8192) {return 8209;}
else if (N <= 16384) {return 16411;}
else if (N <= 32768) {return 32771;}
else if (N <= 65536) {return 65537;}
else if (N <= 131072) {return 131101;}
else if (N <= 262144) {return 262147;}
else if (N <= 524288) {return 524309;}
else if (N <= 1048576) {return 1048583;}
else if (N <= 2097152) {return 2097169;}
else if (N <= 4194304) {return 4194319;}
else if (N <= 8388608) {return 8388617;}
else {return MAX_TABLE_SIZE;} // largest table size that is meaningful (no prime number)
}
static Pixel getPixelVal(const uint8_t* pData, cgif_chan_fmt fmtChan) {
Pixel result;
result.r = *(pData++);
result.g = *(pData++);
result.b = *(pData++);
// check whether there is a alpha channel, if so convert it to a 1 bit alpha channel.
switch(fmtChan) {
case CGIF_CHAN_FMT_RGB:
result.a = 0xFF;
break;
case CGIF_CHAN_FMT_RGBA:
result.a = (*(pData) <= 127) ? 0 : 0xFF;
break;
}
return result;
}
/* get a hash from rgb color values (use open addressing, if entry does not exist, next free spot is returned) */
static int32_t col_hash(const uint8_t* rgb, const uint8_t* hashTable, const uint8_t* indexUsed, const uint32_t tableSize, cgif_chan_fmt fmtChan, uint32_t* cntCollision) {
uint32_t h;
const Pixel pix = getPixelVal(rgb, fmtChan);
*cntCollision = 0; // count the number of collisions
// has alpha channel?
if(pix.a == 0) {
return -1;
}
h = (pix.r * 256 * 256 + pix.g * 256 + pix.b) % tableSize;
while(indexUsed[h] != 0 && memcmp(rgb, &hashTable[3 * h], 3) != 0) { // until free spot is found
++(*cntCollision);
++h;
h = h % tableSize; // start from beginning if there is no free spot at the end
}
return h;
}
/* take array indexed by hash(rgb) and turn it into a dense array */
static uint32_t* hash_to_dense(const uint8_t* pPalette, const uint32_t* arry, uint32_t cnt, const uint8_t* hashTable, const uint8_t* indexUsed, uint32_t tableSize, cgif_chan_fmt fmtChan) {
// pPalette: color table with cnt entries
// arry: array indexed by hash of RGB-value in pPalette
uint32_t* arryDense = malloc(sizeof(uint32_t) * cnt);
uint32_t h, cntCollision;
(void)fmtChan;
for(uint32_t i = 0; i < cnt; ++i) {
h = col_hash(&pPalette[3 * i], hashTable, indexUsed, tableSize, 3, &cntCollision);
arryDense[i] = arry[h];
}
return arryDense;
}
/* get mean of color-cloud along all 3 dimensions (at least one color must be present, otherwise div 0 issue)*/
static void get_mean(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, float* mean){
// pPalette: RGB color palette
// frequ: frequency of the colors (indexing as pPalette, no hashing)
// idxMin, idxMax: palette range for which the mean is computed
// mean: mean value along all three dimensions
float m[3] = {0,0,0};
float sum[3] = {0,0,0};
uint32_t i;
uint8_t dim;
for(i = idxMin; i <= idxMax; ++i) {
for(dim = 0; dim < 3; ++dim) {
sum[dim] += frequ[i];
m[dim] += frequ[i] * pPalette[3 * i + dim];
}
}
for(dim = 0; dim < 3; ++dim) {
mean[dim] = m[dim] / sum[dim];
}
}
/* get variance of color-cloud along all 3 dimensions*/
static void get_variance(const uint8_t* pPalette, const uint32_t* frequ, uint32_t idxMin, uint32_t idxMax, float* var, float* mean){
// pPalette: RGB color palette
// frequ: frequency of the colors (indexing as pPalette, no hashing)
// idxMin, idxMax: palette range for which the variance is computed
// var/mean: variance/mean value along all three dimensions (array with 3 entries)
uint32_t i;
uint8_t dim;
float v[3] = {0,0,0};
float sum[3] = {0,0,0};
get_mean(pPalette, frequ, idxMin, idxMax, mean);
for(i = idxMin; i <= idxMax; ++i) {
for(dim = 0; dim < 3; ++dim) {
sum[dim] += frequ[i];
v[dim] += frequ[i] * (pPalette[3 * i + dim] - mean[dim]) * (pPalette[3 * i + dim] - mean[dim]);
}
}
for(dim = 0; dim < 3; ++dim) {
var[dim] = v[dim] / sum[dim];
}
}
static treeNode* new_tree_node(uint8_t* pPalette, uint32_t* frequ, uint16_t* numLeaveNodes, uint32_t idxMin, uint32_t idxMax, uint8_t colIdx) {
float var[3];
treeNode* node = malloc(sizeof(treeNode));
node->idxMin = idxMin; // minimum color in pPalette belonging to the node
node->idxMax = idxMax; // maximum color in pPalette belonging to the node
get_variance(pPalette, frequ, idxMin, idxMax, var, node->mean);
node->cutDim = argmax64(var, 3); // dimension along which the cut (node split) is done
node->colIdx = colIdx; // index referring to (averaged) color in new color table
node->isLeave = 1; // every new node starts as a leave node
(*numLeaveNodes)++; // increase counter when leave node is added
return node;
}
/* create the decision tree. (Similar to qsort with limited depth: pPalette, frequ get sorted) */
static void crawl_decision_tree(treeNode* root, uint16_t* numLeaveNodes, uint8_t* pPalette, uint32_t* frequ, uint16_t colMax) {
uint32_t i, k, saveNum;
uint16_t nodeIdx = 0;
uint8_t saveBlk[3];
treeNode* parent;
treeNode* nodeList[512]; // array of pointers to nodes for breadth-first tree traversal
nodeList[0] = root;
while(*numLeaveNodes <= (colMax - 1)){
parent = nodeList[nodeIdx++]; // go to next node in the list
if (parent->idxMax - parent->idxMin >= 1) { // condition for node split
i = parent->idxMin; // start of block minimum
k = parent->idxMax; // start at block maximum
while(i < k) { // split parent node in two blocks (like one step in qsort)
for(; pPalette[3 * i + parent->cutDim] <= parent->mean[parent->cutDim]; ++i); // && i<parent->idxMax not needed (other condition is false when i==parent>idxMax since there must be at most 1 element above mean)
for(; pPalette[3 * k + parent->cutDim] > parent->mean[parent->cutDim]; --k); // && k>parent->idxMin not needed (other condition is false when k==parent>idxMin since there must be at most 1 element below mean)
if(k > i) {
memcpy(saveBlk, &(pPalette[3 * i]), 3);
memcpy(&(pPalette[3 * i]), &(pPalette[3 * k]), 3); // swap RGB-blocks in pPalette
memcpy(&(pPalette[3 * k]), saveBlk, 3); // swap RGB-blocks in pPalette
saveNum = frequ[k];
frequ[k] = frequ[i]; // swap also the frequency
frequ[i] = saveNum; // sawp also the frequency
}
}
parent->isLeave = 0; // parent is no leave node anymore when children added
(*numLeaveNodes)--; // decrease counter since parent is removed as a leave node
parent->child0 = new_tree_node(pPalette, frequ, numLeaveNodes, parent->idxMin, i - 1, parent->colIdx); // i-1 is last index of 1st block, one child takes color index from parent
parent->child1 = new_tree_node(pPalette, frequ, numLeaveNodes, i, parent->idxMax, *numLeaveNodes);
nodeList[2*(*numLeaveNodes) - 3] = parent->child0; // add new child nodes to the list (total number of nodes is always 2*(*numLeaveNodes)-1)
nodeList[2*(*numLeaveNodes) - 2] = parent->child1; // add new child nodes to the list (total number of nodes is always 2*(*numLeaveNodes)-1)
}
}
}
/* fill 256 color table using the decision tree */
static void get_palette_from_decision_tree(const treeNode* root, uint8_t* pPalette256){
if(root->isLeave == 0) {
get_palette_from_decision_tree(root->child0, pPalette256);
get_palette_from_decision_tree(root->child1, pPalette256);
} else {
pPalette256[3 * root->colIdx] = (uint8_t)roundf(root->mean[0]);
pPalette256[3 * root->colIdx + 1] = (uint8_t)roundf(root->mean[1]);
pPalette256[3 * root->colIdx + 2] = (uint8_t)roundf(root->mean[2]);
}
}
/* get color index by using the decision tree */
static uint8_t get_leave_node_index(const treeNode* root, const float* rgb) {
if(root->isLeave == 0) { // if there is a split
if(rgb[root->cutDim] <= root->mean[root->cutDim]) {
return get_leave_node_index(root->child0, rgb);
} else {
return get_leave_node_index(root->child1, rgb);
}
} else {
return root->colIdx; // return color index of leave node
}
}
/* color quantization with mean cut method (TBD? switch to median cut)
(works with dense palette, no hash table) */
static treeNode* create_decision_tree(uint8_t* pPalette, uint32_t* pFrequDense, uint8_t* pPalette256, uint32_t cnt, uint16_t colMax, uint8_t depthMax){
uint16_t numLeaveNodes = 0;
treeNode* root = new_tree_node(pPalette, pFrequDense, &numLeaveNodes, 0, cnt - 1, 0);
crawl_decision_tree(root, &numLeaveNodes, pPalette, pFrequDense, colMax);
get_palette_from_decision_tree(root, pPalette256); // fill the reduced color table
return root;
}
/* free memory allocated for the tree */
static void free_decision_tree(treeNode* root){
if(root->isLeave == 0) { // if the node has children
free_decision_tree(root->child0);
free_decision_tree(root->child1);
}
free(root); // free root once free is called for children
}
/* get image with max 256 color indices using Floyd-Steinberg dithering */
static void get_quantized_dithered_image(uint8_t* pImageData, float* pImageDataRGBfloat, uint8_t* pPalette256, treeNode* root, uint32_t numPixel, uint32_t width, uint8_t dithering, uint8_t transIndex, cgif_chan_fmt fmtChan, uint8_t* pBef, cgif_chan_fmt befFmtChan, int hasAlpha) {
// pImageData: image with (max 256) color indices (length: numPixel)
// pImageDataRGBfloat: image with RGB colors (length: 3*numPixel) must be signed to avoid overflow due to error passing, float only needed because of 0.9 factor
// pPalette256: quantized color palette (indexed by node->colIdx), only used if dithering is on
// root: root node of the decision tree for color quantization
// numPixel, width: size of the image
// dithering: 0 (no dithering), 1: Floyd-Steinberg dithering, else: Sierra dithering
uint32_t i;
uint8_t k;
int err; // color errors
/*
we noticed certain artifacts with Floyd-Steinberg dithering if the error is passed too far.
use a damping factor to reduce these effects.
*/
const float factor = 0.90;
if(!dithering) {
for(i = 0; i < numPixel; ++i) {
if(hasAlpha) {
if(pImageDataRGBfloat[fmtChan * i + 3] == 0) {
pImageData[i] = transIndex;
continue;
}
} else {
// do the transparency trick
float* p = pImageDataRGBfloat;
if(pBef && p[i * fmtChan] == pBef[i * befFmtChan] && p[i * fmtChan + 1] == pBef[i * befFmtChan + 1] && p[i * fmtChan + 2] == pBef[i * befFmtChan + 2]) {
pImageData[i] = transIndex;
continue;
}
}
pImageData[i] = get_leave_node_index(root, &pImageDataRGBfloat[fmtChan * i]); // use decision tree to get indices for new colors
}
} else {
for(i = 0; i < numPixel; ++i) {
if(fmtChan == CGIF_CHAN_FMT_RGBA) {
if(pImageDataRGBfloat[fmtChan * i + 3] == 0) {
pImageData[i] = transIndex;
continue;
}
}
// TBD add the transparency trick
// restrict color + error to 0-255 interval
for(k = 0; k<3; ++k) {
pImageDataRGBfloat[fmtChan * i + k] = MAX(0,MIN(pImageDataRGBfloat[fmtChan * i + k], 255)); // cut to 0-255 before
}
pImageData[i] = get_leave_node_index(root, &pImageDataRGBfloat[fmtChan * i]); // use decision tree to get indices for new colors
for(k = 0; k<3; ++k) {
err = pImageDataRGBfloat[fmtChan * i + k] - pPalette256[3 * pImageData[i] + k]; // compute color error
//diffuse error with Floyd-Steinberg dithering.
if(dithering == 1) {
if(i % width < width-1){
pImageDataRGBfloat[fmtChan * (i+1) + k] += factor * (7*err >> 4);
}
if(i < numPixel - width){
pImageDataRGBfloat[fmtChan * (i+width) + k] += factor * (5*err >> 4);
if(i % width > 0){
pImageDataRGBfloat[fmtChan * (i+width-1) + k] += factor * (3*err >> 4);
}
if(i % width < width-1){
pImageDataRGBfloat[fmtChan * (i+width+1) + k] += factor * (1*err >> 4);
}
}
} else {
// Sierra dithering
if(i % width < width-1){
pImageDataRGBfloat[fmtChan * (i+1) + k] += factor * (5*err >> 5);
if(i % width < width-2){
pImageDataRGBfloat[fmtChan * (i+2) + k] += factor * (3*err >> 5);
}
}
if(i < numPixel - width){
pImageDataRGBfloat[fmtChan * (i+width) + k] += factor * (5*err >> 5);
if(i % width > 0){
pImageDataRGBfloat[fmtChan * (i+width-1) + k] += factor * (4*err >> 5);
if(i % width > 1){
pImageDataRGBfloat[fmtChan * (i+width-2) + k] += factor * (2*err >> 5);
}
}
if(i % width < width-1){
pImageDataRGBfloat[fmtChan * (i+width+1) + k] += factor * (4*err >> 5);
if(i % width < width-2){
pImageDataRGBfloat[fmtChan * (i+width+2) + k] += factor * (2*err >> 5);
}
}
if(i < numPixel - 2*width){
pImageDataRGBfloat[fmtChan * (i+2*width) + k] += factor * (3*err >> 5);
if(i % width > 0){
pImageDataRGBfloat[fmtChan * (i+2*width-1) + k] += factor * (2*err >> 5);
}
if(i % width < width-1){
pImageDataRGBfloat[fmtChan * (i+2*width+1) + k] += factor * (2*err >> 5);
}
}
}
}
}
}
}
}
/* convert rgb image to indices and palette
The overall number of colors must already be <= 256 which is e.g. the case when the image comes directly from a GIF */
// pImageDataRGB: image in RGB (input)
// numPixel,width:number of pixels of input image, width (only used for dithering)
// fmtChan: select if image data are RGB or RGBa
// pImageData: pointer to image as indices (output)
// palette: pointer to color palette (output)
// pPalette256: new color palette with 256 colors max.
// depthMax: maximum depth of decision tree for colors quantization (sets number of colors)
// dithering: 0 (no dithering), 1: Floyd-Steinberg dithering, else: Sierra dithering
static
uint32_t rgb_to_index(const uint8_t* pImageDataRGB, uint32_t numPixel, uint32_t width, cgif_chan_fmt fmtChan, uint8_t* pImageData, uint8_t* pPalette256, uint8_t depthMax, uint8_t dithering, int* pHasAlpha, uint8_t* pBef, cgif_chan_fmt befFmtChan) {
uint32_t i,j,h;
uint32_t cntCollision; // count the number of collisions
uint32_t cnt = 0; // count the number of colors
uint32_t tableSize = 262147; // initial size of the hash table
uint32_t tableSizeNew; // size of hash-table when increasing its size
tableSize = getNextPrimePower2(tableSize); // increase table size to next prime number
int hasAlpha = 0;
uint8_t transIndex;
uint32_t* colIdx = malloc(sizeof(uint32_t)*tableSize); // index of the color
uint32_t* frequ = malloc(sizeof(uint32_t)*tableSize); // frequency of colors (histogram)
uint8_t* hashTable = malloc(3 * tableSize); // stores RGB vales at position determined by hash
uint8_t* indexUsed = malloc(tableSize); // which part of the hash table was already used
uint8_t* hashTable_new; // for new hash table
uint8_t* indexUsed_new; // for new hash table
uint32_t* frequ_new; // for new hash table
uint8_t* pPalette = malloc(3 * tableSize); // color palette with all the colors (before color quantization)
const uint8_t sizePixel = fmtChan; // number of bytes for one pixel (e.g. 3 for RGB, 4 for RGBa)
memset(pPalette, 0, 3 * tableSize); // unused part of color table is uninitialized otheriwse
memset(indexUsed, 0, tableSize); // initially no entry in hash-table is used
for(i = 0; i < numPixel; ++i) {
h = col_hash(&pImageDataRGB[sizePixel * i], hashTable, indexUsed, tableSize, fmtChan, &cntCollision);
if(h == -1) { // -1 means that the user has set a user-defined transparency (alpha channel)
hasAlpha = 1;
continue; // do not take this pixel into account (e.g. alpha channel present)
}
if(indexUsed[h] == 0) {
memcpy(&hashTable[3 * h], &pImageDataRGB[sizePixel * i], 3); // add new color to hash table
memcpy(&pPalette[3 * cnt], &pImageDataRGB[sizePixel * i], 3); // add new color to palette
indexUsed[h] = 1; // mark hash table entry as used
colIdx[h] = cnt; // assign index to color
frequ[h] = 1; // the new color occured 1x (1st time)
++cnt; // one new color added
} else { // if color exists already
frequ[h] += 1; // increment color histrogram
}
// resize the hash table (if more than half-full)
if((cnt > (tableSize >> 1) || cntCollision > MAX_COLLISIONS) && tableSize < MAX_TABLE_SIZE) {
tableSizeNew = getNextPrimePower2(tableSize); // increase table size to the next prime number above the next power of two
pPalette = realloc(pPalette, 3 * tableSizeNew);
colIdx = realloc(colIdx, sizeof(uint32_t) * tableSizeNew);
hashTable_new = malloc(3 * tableSizeNew);
indexUsed_new = malloc(tableSizeNew);
frequ_new = malloc(sizeof(uint32_t) * tableSizeNew);
memset(indexUsed_new, 0, tableSizeNew);
cnt = 0;
for(j = 0; j < tableSize; ++j) { // TBD: wouldn't it be easier to loop over pPalette and also leave pPalette in place?, if indexUsed is also unnecessary then
if(indexUsed[j] == 1){
h = col_hash(&hashTable[3 * j], hashTable_new, indexUsed_new, tableSizeNew, 3, &cntCollision); // recompute hash with new table size
memcpy(&hashTable_new[3 * h], &hashTable[3 * j], 3); // put value from old hash table to right position of the new one
memcpy(&pPalette[3 * cnt], &hashTable[3 * j], 3);
indexUsed_new[h] = 1;
frequ_new[h] = frequ[j];
colIdx[h] = cnt;
++cnt;
}
}
tableSize = tableSizeNew;
free(hashTable); // free old hash table that is not used anymore
free(indexUsed);
free(frequ);
hashTable = hashTable_new; // pass pointer to new hash table
indexUsed = indexUsed_new;
frequ = frequ_new;
}
}
const uint16_t colMax = (1uL << depthMax) - 1; // maximum number of colors (-1 to leave space for transparency), disadvantage (TBD): quantization for static image with 256 colors and no alpha channel unnecessary
if(cnt > colMax) { // color-quantization is needed
uint32_t* pFrequDense = hash_to_dense(pPalette, frequ, cnt, hashTable, indexUsed, tableSize, fmtChan);
treeNode* root = create_decision_tree(pPalette, pFrequDense, pPalette256, cnt, colMax, depthMax); // create decision tree (dynamic, splits along rgb-dimension with highest variance)
float* pImageDataRGBfloat = malloc(fmtChan * numPixel * sizeof(float)); // TBD fmtChan + only when hasAlpha
for(i = 0; i < fmtChan * numPixel; ++i){
pImageDataRGBfloat[i] = pImageDataRGB[i];
}
transIndex = colMax;
get_quantized_dithered_image(pImageData, pImageDataRGBfloat, pPalette256, root, numPixel, width, dithering, transIndex, fmtChan, pBef, befFmtChan, hasAlpha); // do color quantization and dithering
free(pImageDataRGBfloat); // RGB image is not needed anymore
free_decision_tree(root); // tree for color quantization is not needed anymore
free(pFrequDense);
cnt = colMax;
} else { // no color-quantization is needed if the number of colors is small enough
transIndex = cnt;
for(i = 0; i < numPixel; ++i) {
if(!hasAlpha && pBef) {
if(memcmp(&pImageData[sizePixel * i], &pBef[befFmtChan * i], 3) == 0) {
pImageData[i] = transIndex;
continue;
}
}
h = col_hash(&pImageDataRGB[sizePixel * i], hashTable, indexUsed, tableSize, fmtChan, &cntCollision);
pImageData[i] = (h == -1) ? transIndex : colIdx[h];
}
memcpy(pPalette256, pPalette, 3 * 256); // keep the color palette (no quantization)
}
*pHasAlpha = hasAlpha;
free(colIdx);
free(hashTable);
free(indexUsed);
free(pPalette);
free(frequ);
return cnt; // return number of colors found
}
CGIFrgb* cgif_rgb_newgif(const CGIFrgb_Config* pConfig) {
CGIF_Config idxConfig = {0};
CGIFrgb* pGIFrgb;
pGIFrgb = malloc(sizeof(CGIFrgb));
memset(pGIFrgb, 0, sizeof(CGIFrgb));
idxConfig.pWriteFn = pConfig->pWriteFn;
idxConfig.pContext = pConfig->pContext;
idxConfig.path = pConfig->path;
idxConfig.numLoops = pConfig->numLoops;
idxConfig.width = pConfig->width;
idxConfig.height = pConfig->height;
idxConfig.attrFlags = CGIF_ATTR_IS_ANIMATED | CGIF_ATTR_NO_GLOBAL_TABLE;
pGIFrgb->pGIF = cgif_newgif(&idxConfig);
if(pGIFrgb->pGIF == NULL) {
free(pGIFrgb);
return NULL;
}
pGIFrgb->config = *pConfig;
return pGIFrgb;
}
cgif_result cgif_rgb_addframe(CGIFrgb* pGIF, const CGIFrgb_FrameConfig* pConfig) {
uint8_t aPalette[256 * 3];
memset(aPalette, 0, 256 * 3); // rgb_to_index does not necessarily fill/use all palette entries (-> initialize aPalette to avoid underfined behaviour)
CGIF_FrameConfig fConfig = {0};
uint8_t* pNewBef;
const uint16_t imageWidth = pGIF->config.width;
const uint16_t imageHeight = pGIF->config.height;
const uint32_t numPixel = MULU16(imageWidth, imageHeight);
int hasAlpha;
pNewBef = malloc(pConfig->fmtChan * MULU16(imageWidth, imageHeight));
memcpy(pNewBef, pConfig->pImageData, pConfig->fmtChan * MULU16(imageWidth, imageHeight));
fConfig.pLocalPalette = aPalette;
fConfig.pImageData = malloc(pGIF->config.width * (uint32_t)pGIF->config.height);
fConfig.delay = pConfig->delay;
fConfig.attrFlags = CGIF_FRAME_ATTR_USE_LOCAL_TABLE;
const int sizeLCT = rgb_to_index(pConfig->pImageData, numPixel, pGIF->config.width, pConfig->fmtChan, fConfig.pImageData, aPalette, 8, 1, &hasAlpha, pGIF->pBefImageData, pGIF->befFmtChan);
fConfig.numLocalPaletteEntries = sizeLCT;
if(hasAlpha) {
fConfig.attrFlags |= CGIF_FRAME_ATTR_HAS_ALPHA;
fConfig.transIndex = sizeLCT;
} else {
fConfig.attrFlags |= CGIF_FRAME_ATTR_HAS_SET_TRANS;
fConfig.transIndex = sizeLCT;
}
cgif_result r = cgif_addframe(pGIF->pGIF, &fConfig);
free(pGIF->pBefImageData);
pGIF->pBefImageData = pNewBef;
pGIF->befFmtChan = pConfig->fmtChan;
// cleanup
free(fConfig.pImageData);
return r;
}
cgif_result cgif_rgb_close(CGIFrgb* pGIF) {
cgif_result r = cgif_close(pGIF->pGIF);
free(pGIF->pBefImageData);
free(pGIF);
return r;
}

View File

@ -1,10 +0,0 @@
INCLUDEPATH += $$PWD/giflib
SOURCES += $$PWD/giflib/dgif_lib.c \
$$PWD/giflib/egif_lib.c \
$$PWD/giflib/gif_err.c \
$$PWD/giflib/gif_hash.c \
$$PWD/giflib/gifalloc.c \
$$PWD/giflib/quantize.c \
$$PWD/giflib/openbsd-reallocarray.c

View File

@ -15,17 +15,17 @@
## WingGifEditor
&emsp;&emsp;本软件是基于 QT 编写的 GIF 编辑器,采用 C++ 进行开发,目的是让 Deepin 上具有简单易用的 GIF 编辑器。`Windows`上有一个十分强大都既是 GIF 录屏工具又是强大 GIF 编辑器`ScreenToGif`。但 Linux 上只有强大的 GIF 录屏工具,但没有简单好用满足基本编辑需求的 GIF 编辑器。于是乎,我给开发了一个简易的 GIF 编辑器,它具有基本的编辑功能,简单方便。
&emsp;&emsp;本软件是基于 QT 编写的 GIF 编辑器,采用 C++ 进行开发,目的是让 Deepin 上具有简单易用的 GIF 编辑器。`Windows`上有一个十分强大都既是 GIF 录屏工具又是强大 GIF 编辑器`ScreenToGif`。但 Linux 上只有强大的 GIF 录屏工具,但没有简单好用满足基本编辑需求带有图形界面的 GIF 编辑器。于是乎,我给开发了一个简易的 GIF 编辑器,它具有基本的编辑功能,简单方便。
&emsp;&emsp;有关本软件提建议的时候不要天马行空,本软件的定位是提供简单易用满足基本需求的 GIF 编辑器,并不是像`ScreenToGif`,一是没有相关基础知识,自己一个人搞不动;二是没必要,我不想把这个软件搞个大而全,配合`Gimp`等优秀的图像编辑器作为辅助,来编辑 GIF 图像绰绰有余了。
### GIF 库
&emsp;&emsp;本软件基于两个库修改定制而得,一个负责读:`QtGifImage`,另一个负责写:`GifEncoder`。
&emsp;&emsp;本软件基于两个库修改定制而得,一个负责读:`QtGifImage`,另一个负责写:`cgif`。
&emsp;&emsp;`QtGifImage`是`Qt`上的`GIF`读写库,可以将`GIF`解析为`QImage`序列,基于`giflib`。这个我改动比较大,只保留了读取部分,并与我的软件做了适配(这个库写`GIF`图片的质量有点太差),仓库 [链接](https://github.com/jiangcaiyang/QtGifImage) 。
&emsp;&emsp;`GifEncoder`是一个能够提供高质量编码的`GIF`编码库,使用神经网络算法。为了实现基本的压缩,我进行略微的修改定制得到,仓库 [链接](https://github.com/xiaozhuai/GifEncoder) 。
&emsp;&emsp;`cgif`一个快速轻量级的GIF编码器可以创建 GIF 动画和图像,仓库 [链接](https://github.com/dloebl/cgif) 。
### 编译安装

View File

@ -35,7 +35,20 @@ SOURCES += \
UndoCommand/replaceframecommand.cpp \
UndoCommand/cropimagecommand.cpp \
Dialog/waitingdialog.cpp \
settingdialog.cpp \
GifImage/decoder/gifdecoder.cpp \
GifImage/decoder/giflib/dgif_lib.c \
GifImage/decoder/giflib/egif_lib.c \
GifImage/decoder/giflib/gif_err.c \
GifImage/decoder/giflib/gif_font.c \
GifImage/decoder/giflib/gif_hash.c \
GifImage/decoder/giflib/gifalloc.c \
GifImage/decoder/giflib/openbsd-reallocarray.c \
GifImage/decoder/giflib/quantize.c \
GifImage/encoder/src/cgif.c \
GifImage/encoder/src/cgif_raw.c \
GifImage/encoder/gifencoder.cpp \
GifImage/encoder/src/cgif_rgb.c \
Dialog/settingdialog.cpp \
Class/settings.cpp
RESOURCES += resources.qrc
@ -68,6 +81,14 @@ HEADERS += Dialog/mainwindow.h \
UndoCommand/replaceframecommand.h \
UndoCommand/cropimagecommand.h \
Dialog/waitingdialog.h \
GifImage/decoder/giflib/gif_hash.h \
GifImage/decoder/giflib/gif_lib.h \
GifImage/decoder/giflib/gif_lib_private.h \
GifImage/decoder/gifdecoder.h \
GifImage/encoder/inc/cgif.h \
GifImage/encoder/inc/cgif_raw.h \
GifImage/encoder/gifencoder.h \
Dialog/settingdialog.h \
Class/settings.h
DISTFILES +=
@ -75,4 +96,5 @@ DISTFILES +=
TRANSLATIONS += \
$$PWD/lang/zh.ts
include($$PWD/GifImage/WingGif.pri)
INCLUDEPATH += $$PWD/GifImage/decoder/giflib
INCLUDEPATH += $$PWD/GifImage/encoder/inc

View File

@ -1,16 +1,16 @@
## WingGifEditor
&emsp;&emsp;本软件是基于 QT 编写的 GIF 编辑器,采用 C++ 进行开发,目的是让 Deepin 上具有简单易用的 GIF 编辑器。`Windows`上有一个十分强大都既是 GIF 录屏工具又是强大 GIF 编辑器`ScreenToGif`。但 Linux 上只有强大的 GIF 录屏工具,但没有简单好用满足基本编辑需求的 GIF 编辑器。于是乎,我给开发了一个简易的 GIF 编辑器,它具有基本的编辑功能,简单方便。
&emsp;&emsp;本软件是基于 QT 编写的 GIF 编辑器,采用 C++ 进行开发,目的是让 Deepin 上具有简单易用的 GIF 编辑器。`Windows`上有一个十分强大都既是 GIF 录屏工具又是强大 GIF 编辑器`ScreenToGif`。但 Linux 上只有强大的 GIF 录屏工具,但没有简单好用满足基本编辑需求带有图形界面的 GIF 编辑器。于是乎,我给开发了一个简易的 GIF 编辑器,它具有基本的编辑功能,简单方便。
&emsp;&emsp;有关本软件提建议的时候不要天马行空,本软件的定位是提供简单易用满足基本需求的 GIF 编辑器,并不是像`ScreenToGif`,一是没有相关基础知识,自己一个人搞不动;二是没必要,我不想把这个软件搞个大而全,配合`Gimp`等优秀的图像编辑器作为辅助,来编辑 GIF 图像绰绰有余了。
### GIF 库
&emsp;&emsp;本软件基于两个库修改定制而得,一个负责读:`QtGifImage`,另一个负责写:`GifEncoder`。
&emsp;&emsp;本软件基于两个库修改定制而得,一个负责读:`QtGifImage`,另一个负责写:`cgif`。
&emsp;&emsp;`QtGifImage`是`Qt`上的`GIF`读写库,可以将`GIF`解析为`QImage`序列,基于`giflib`。这个我改动比较大,只保留了读取部分,并与我的软件做了适配(这个库写`GIF`图片的质量有点太差),仓库 [链接](https://github.com/jiangcaiyang/QtGifImage) 。
&emsp;&emsp;`GifEncoder`是一个能够提供高质量编码的`GIF`编码库,使用神经网络算法。为了实现基本的压缩,我进行略微的修改定制得到,仓库 [链接](https://github.com/xiaozhuai/GifEncoder) 。
&emsp;&emsp;`cgif`一个快速轻量级的GIF编码器可以创建 GIF 动画和图像,仓库 [链接](https://github.com/dloebl/cgif) 。
### 编译安装

Binary file not shown.

View File

@ -81,612 +81,602 @@
<context>
<name>GifDecoder</name>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="160"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="190"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="166"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="196"/>
<source>Failed to open given file</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="163"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="169"/>
<source>Failed to write to given file</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="166"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="172"/>
<source>Screen descriptor has already been set</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="169"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="175"/>
<source>Image descriptor is still active</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="172"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="205"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="178"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="211"/>
<source>Neither global nor local color map</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="175"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="211"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="181"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="217"/>
<source>Number of pixels bigger than width * height</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="178"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="214"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="184"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="220"/>
<source>Failed to allocate required memory</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="181"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="187"/>
<source>Write failed (disk full?)</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="184"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="217"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="190"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="223"/>
<source>Failed to close given file</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="187"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="193"/>
<source>Given file was not opened for write</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="193"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="199"/>
<source>Failed to read from given file</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="196"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="202"/>
<source>Data is not in GIF format</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="199"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="205"/>
<source>No screen descriptor detected</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="202"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="208"/>
<source>No Image Descriptor detected</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="208"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="214"/>
<source>Wrong record type detected</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="220"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="226"/>
<source>Given file was not opened for read</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="223"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="229"/>
<source>Image is defective, decoding aborted</source>
<translation></translation>
</message>
<message>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="226"/>
<location filename="../GifImage/decoder/gifdecoder.cpp" line="232"/>
<source>Image EOF detected before image complete</source>
<translation></translation>
</message>
</context>
<context>
<name>GifImage</name>
<message>
<source>Unknown Error!</source>
<translation type="vanished"></translation>
</message>
<message>
<source>UnSolvedBug</source>
<translation type="vanished"> Bug</translation>
</message>
<message>
<source>GIFUnSolveBugInfo</source>
<translation type="vanished"> deconstructImages GIF 使 GIF </translation>
</message>
<message>
<source>Error</source>
<translation type="vanished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../Dialog/mainwindow.cpp" line="52"/>
<location filename="../Dialog/mainwindow.cpp" line="82"/>
<location filename="../Dialog/mainwindow.cpp" line="53"/>
<location filename="../Dialog/mainwindow.cpp" line="83"/>
<source>WingGifEditor</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="147"/>
<location filename="../Dialog/mainwindow.cpp" line="148"/>
<source>File</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="151"/>
<location filename="../Dialog/mainwindow.cpp" line="152"/>
<source>New</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="152"/>
<location filename="../Dialog/mainwindow.cpp" line="345"/>
<location filename="../Dialog/mainwindow.cpp" line="153"/>
<location filename="../Dialog/mainwindow.cpp" line="346"/>
<source>NewFromPics</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="153"/>
<location filename="../Dialog/mainwindow.cpp" line="346"/>
<location filename="../Dialog/mainwindow.cpp" line="154"/>
<location filename="../Dialog/mainwindow.cpp" line="347"/>
<source>NewFromGifs</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="156"/>
<location filename="../Dialog/mainwindow.cpp" line="354"/>
<location filename="../Dialog/mainwindow.cpp" line="157"/>
<location filename="../Dialog/mainwindow.cpp" line="355"/>
<source>Open</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="158"/>
<location filename="../Dialog/mainwindow.cpp" line="355"/>
<location filename="../Dialog/mainwindow.cpp" line="159"/>
<location filename="../Dialog/mainwindow.cpp" line="356"/>
<source>Save</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="161"/>
<location filename="../Dialog/mainwindow.cpp" line="357"/>
<location filename="../Dialog/mainwindow.cpp" line="162"/>
<location filename="../Dialog/mainwindow.cpp" line="358"/>
<source>SaveAs</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="164"/>
<location filename="../Dialog/mainwindow.cpp" line="359"/>
<location filename="../Dialog/mainwindow.cpp" line="165"/>
<location filename="../Dialog/mainwindow.cpp" line="360"/>
<source>Export</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="167"/>
<location filename="../Dialog/mainwindow.cpp" line="168"/>
<source>Close</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="173"/>
<location filename="../Dialog/mainwindow.cpp" line="174"/>
<source>Edit</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="175"/>
<location filename="../Dialog/mainwindow.cpp" line="362"/>
<location filename="../Dialog/mainwindow.cpp" line="176"/>
<location filename="../Dialog/mainwindow.cpp" line="363"/>
<source>Undo</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="179"/>
<location filename="../Dialog/mainwindow.cpp" line="365"/>
<location filename="../Dialog/mainwindow.cpp" line="180"/>
<location filename="../Dialog/mainwindow.cpp" line="366"/>
<source>Redo</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="184"/>
<location filename="../Dialog/mainwindow.cpp" line="368"/>
<location filename="../Dialog/mainwindow.cpp" line="185"/>
<location filename="../Dialog/mainwindow.cpp" line="369"/>
<source>Cut</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="187"/>
<location filename="../Dialog/mainwindow.cpp" line="370"/>
<location filename="../Dialog/mainwindow.cpp" line="188"/>
<location filename="../Dialog/mainwindow.cpp" line="371"/>
<source>Copy</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="190"/>
<location filename="../Dialog/mainwindow.cpp" line="372"/>
<location filename="../Dialog/mainwindow.cpp" line="191"/>
<location filename="../Dialog/mainwindow.cpp" line="373"/>
<source>Paste</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="193"/>
<location filename="../Dialog/mainwindow.cpp" line="374"/>
<location filename="../Dialog/mainwindow.cpp" line="194"/>
<location filename="../Dialog/mainwindow.cpp" line="375"/>
<source>Delete</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="197"/>
<location filename="../Dialog/mainwindow.cpp" line="376"/>
<location filename="../Dialog/mainwindow.cpp" line="198"/>
<location filename="../Dialog/mainwindow.cpp" line="377"/>
<source>SelectAll</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="200"/>
<location filename="../Dialog/mainwindow.cpp" line="378"/>
<location filename="../Dialog/mainwindow.cpp" line="201"/>
<location filename="../Dialog/mainwindow.cpp" line="379"/>
<source>Deselect</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="203"/>
<location filename="../Dialog/mainwindow.cpp" line="380"/>
<location filename="../Dialog/mainwindow.cpp" line="204"/>
<location filename="../Dialog/mainwindow.cpp" line="381"/>
<source>ReverseSelection</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="207"/>
<location filename="../Dialog/mainwindow.cpp" line="382"/>
<location filename="../Dialog/mainwindow.cpp" line="941"/>
<location filename="../Dialog/mainwindow.cpp" line="208"/>
<location filename="../Dialog/mainwindow.cpp" line="383"/>
<location filename="../Dialog/mainwindow.cpp" line="860"/>
<source>Goto</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="213"/>
<location filename="../Dialog/mainwindow.cpp" line="220"/>
<location filename="../Dialog/mainwindow.cpp" line="436"/>
<location filename="../Dialog/mainwindow.cpp" line="214"/>
<location filename="../Dialog/mainwindow.cpp" line="221"/>
<location filename="../Dialog/mainwindow.cpp" line="437"/>
<source>Play</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="214"/>
<location filename="../Dialog/mainwindow.cpp" line="432"/>
<location filename="../Dialog/mainwindow.cpp" line="215"/>
<location filename="../Dialog/mainwindow.cpp" line="433"/>
<source>FirstFrame</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="217"/>
<location filename="../Dialog/mainwindow.cpp" line="434"/>
<location filename="../Dialog/mainwindow.cpp" line="218"/>
<location filename="../Dialog/mainwindow.cpp" line="435"/>
<source>LastFrame</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="223"/>
<location filename="../Dialog/mainwindow.cpp" line="438"/>
<location filename="../Dialog/mainwindow.cpp" line="224"/>
<location filename="../Dialog/mainwindow.cpp" line="439"/>
<source>Stop</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="225"/>
<location filename="../Dialog/mainwindow.cpp" line="440"/>
<location filename="../Dialog/mainwindow.cpp" line="226"/>
<location filename="../Dialog/mainwindow.cpp" line="441"/>
<source>NextFrame</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="228"/>
<location filename="../Dialog/mainwindow.cpp" line="442"/>
<location filename="../Dialog/mainwindow.cpp" line="229"/>
<location filename="../Dialog/mainwindow.cpp" line="443"/>
<source>EndFrame</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="235"/>
<location filename="../Dialog/mainwindow.cpp" line="236"/>
<source>Picture</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="236"/>
<location filename="../Dialog/mainwindow.cpp" line="395"/>
<location filename="../Dialog/mainwindow.cpp" line="237"/>
<location filename="../Dialog/mainwindow.cpp" line="396"/>
<source>ReduceFrame</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="239"/>
<location filename="../Dialog/mainwindow.cpp" line="397"/>
<location filename="../Dialog/mainwindow.cpp" line="240"/>
<location filename="../Dialog/mainwindow.cpp" line="398"/>
<source>DeleteBefore</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="242"/>
<location filename="../Dialog/mainwindow.cpp" line="399"/>
<location filename="../Dialog/mainwindow.cpp" line="243"/>
<location filename="../Dialog/mainwindow.cpp" line="400"/>
<source>DeleteAfter</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="246"/>
<location filename="../Dialog/mainwindow.cpp" line="401"/>
<location filename="../Dialog/mainwindow.cpp" line="247"/>
<location filename="../Dialog/mainwindow.cpp" line="402"/>
<source>MoveLeft</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="249"/>
<location filename="../Dialog/mainwindow.cpp" line="403"/>
<location filename="../Dialog/mainwindow.cpp" line="250"/>
<location filename="../Dialog/mainwindow.cpp" line="404"/>
<source>MoveRight</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="252"/>
<location filename="../Dialog/mainwindow.cpp" line="405"/>
<location filename="../Dialog/mainwindow.cpp" line="253"/>
<location filename="../Dialog/mainwindow.cpp" line="406"/>
<source>Reverse</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="256"/>
<location filename="../Dialog/mainwindow.cpp" line="408"/>
<location filename="../Dialog/mainwindow.cpp" line="257"/>
<location filename="../Dialog/mainwindow.cpp" line="409"/>
<source>SetDelay</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="259"/>
<location filename="../Dialog/mainwindow.cpp" line="410"/>
<location filename="../Dialog/mainwindow.cpp" line="260"/>
<location filename="../Dialog/mainwindow.cpp" line="411"/>
<source>InsertPics</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="261"/>
<location filename="../Dialog/mainwindow.cpp" line="412"/>
<location filename="../Dialog/mainwindow.cpp" line="262"/>
<location filename="../Dialog/mainwindow.cpp" line="413"/>
<source>MergeGIfs</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="263"/>
<location filename="../Dialog/mainwindow.cpp" line="414"/>
<location filename="../Dialog/mainwindow.cpp" line="264"/>
<location filename="../Dialog/mainwindow.cpp" line="415"/>
<source>ScaleGif</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="266"/>
<location filename="../Dialog/mainwindow.cpp" line="416"/>
<location filename="../Dialog/mainwindow.cpp" line="267"/>
<location filename="../Dialog/mainwindow.cpp" line="417"/>
<source>CutGif</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="270"/>
<location filename="../Dialog/mainwindow.cpp" line="419"/>
<location filename="../Dialog/mainwindow.cpp" line="271"/>
<location filename="../Dialog/mainwindow.cpp" line="420"/>
<source>FilpH</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="272"/>
<location filename="../Dialog/mainwindow.cpp" line="421"/>
<location filename="../Dialog/mainwindow.cpp" line="273"/>
<location filename="../Dialog/mainwindow.cpp" line="422"/>
<source>FlipV</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="274"/>
<location filename="../Dialog/mainwindow.cpp" line="423"/>
<location filename="../Dialog/mainwindow.cpp" line="275"/>
<location filename="../Dialog/mainwindow.cpp" line="424"/>
<source>RotateLeft</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="276"/>
<location filename="../Dialog/mainwindow.cpp" line="425"/>
<location filename="../Dialog/mainwindow.cpp" line="277"/>
<location filename="../Dialog/mainwindow.cpp" line="426"/>
<source>RotateR</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="281"/>
<location filename="../Dialog/mainwindow.cpp" line="282"/>
<source>Effect</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="283"/>
<location filename="../Dialog/mainwindow.cpp" line="449"/>
<location filename="../Dialog/mainwindow.cpp" line="284"/>
<location filename="../Dialog/mainwindow.cpp" line="450"/>
<source>ExportBlank</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="285"/>
<location filename="../Dialog/mainwindow.cpp" line="451"/>
<location filename="../Dialog/mainwindow.cpp" line="286"/>
<location filename="../Dialog/mainwindow.cpp" line="452"/>
<source>ApplyModel</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="288"/>
<location filename="../Dialog/mainwindow.cpp" line="454"/>
<location filename="../Dialog/mainwindow.cpp" line="289"/>
<location filename="../Dialog/mainwindow.cpp" line="455"/>
<source>CreateReverse</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="291"/>
<location filename="../Dialog/mainwindow.cpp" line="456"/>
<location filename="../Dialog/mainwindow.cpp" line="292"/>
<location filename="../Dialog/mainwindow.cpp" line="457"/>
<source>ScaleDelay</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="294"/>
<location filename="../Dialog/mainwindow.cpp" line="1435"/>
<location filename="../Dialog/mainwindow.cpp" line="295"/>
<location filename="../Dialog/mainwindow.cpp" line="1359"/>
<source>OnionMask</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="299"/>
<location filename="../Dialog/mainwindow.cpp" line="300"/>
<source>Author</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="301"/>
<location filename="../Dialog/mainwindow.cpp" line="385"/>
<location filename="../Dialog/mainwindow.cpp" line="302"/>
<location filename="../Dialog/mainwindow.cpp" line="386"/>
<source>Setting</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="302"/>
<location filename="../Dialog/mainwindow.cpp" line="303"/>
<source>FullScreen</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="305"/>
<location filename="../Dialog/mainwindow.cpp" line="386"/>
<location filename="../Dialog/mainwindow.cpp" line="306"/>
<location filename="../Dialog/mainwindow.cpp" line="387"/>
<source>About</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="306"/>
<location filename="../Dialog/mainwindow.cpp" line="387"/>
<location filename="../Dialog/mainwindow.cpp" line="307"/>
<location filename="../Dialog/mainwindow.cpp" line="388"/>
<source>Sponsor</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="307"/>
<location filename="../Dialog/mainwindow.cpp" line="388"/>
<location filename="../Dialog/mainwindow.cpp" line="308"/>
<location filename="../Dialog/mainwindow.cpp" line="389"/>
<source>Wiki</source>
<translation> Wiki</translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="308"/>
<location filename="../Dialog/mainwindow.cpp" line="309"/>
<source>AboutQT</source>
<translation> QT</translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="491"/>
<location filename="../Dialog/mainwindow.cpp" line="492"/>
<source>InfoSave</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="494"/>
<location filename="../Dialog/mainwindow.cpp" line="495"/>
<source>InfoReadWrite</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="496"/>
<location filename="../Dialog/mainwindow.cpp" line="497"/>
<source>FitInView</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="660"/>
<location filename="../Dialog/mainwindow.cpp" line="658"/>
<source>OpenGif</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="665"/>
<location filename="../Dialog/mainwindow.cpp" line="663"/>
<source>OpenError</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="724"/>
<location filename="../Dialog/mainwindow.cpp" line="722"/>
<source>ConfirmClose</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="725"/>
<location filename="../Dialog/mainwindow.cpp" line="723"/>
<source>ConfirmSave</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="726"/>
<location filename="../Dialog/mainwindow.cpp" line="724"/>
<source>Untitle</source>
<translation>.gif</translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="743"/>
<location filename="../Dialog/mainwindow.cpp" line="741"/>
<source>%1 frame | %2 total</source>
<translation> %1 | %2 </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="850"/>
<location filename="../Dialog/mainwindow.cpp" line="769"/>
<source>NewFromPicsGif</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="869"/>
<location filename="../Dialog/mainwindow.cpp" line="788"/>
<source>NewFromGifsGif</source>
<translation> GIF </translation>
</message>
<message>
<source>PleaseWait!</source>
<translation type="vanished"></translation>
<location filename="../Dialog/mainwindow.cpp" line="1390"/>
<source>Error</source>
<translation></translation>
</message>
<message>
<source>ProcessSuccess</source>
<translation type="vanished"></translation>
<location filename="../Dialog/mainwindow.cpp" line="1390"/>
<source>ImagingError</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="885"/>
<location filename="../Dialog/mainwindow.cpp" line="1267"/>
<location filename="../Dialog/mainwindow.cpp" line="1283"/>
<location filename="../Dialog/mainwindow.cpp" line="1365"/>
<location filename="../Dialog/mainwindow.cpp" line="1396"/>
<location filename="../Dialog/mainwindow.cpp" line="1395"/>
<source>InvaildTerminal</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="804"/>
<location filename="../Dialog/mainwindow.cpp" line="1191"/>
<location filename="../Dialog/mainwindow.cpp" line="1207"/>
<location filename="../Dialog/mainwindow.cpp" line="1289"/>
<location filename="../Dialog/mainwindow.cpp" line="1320"/>
<source>ChooseFile</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="898"/>
<location filename="../Dialog/mainwindow.cpp" line="817"/>
<source>InvalidGif</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="941"/>
<location filename="../Dialog/mainwindow.cpp" line="1435"/>
<location filename="../Dialog/mainwindow.cpp" line="860"/>
<location filename="../Dialog/mainwindow.cpp" line="1359"/>
<source>PleaseInputIndex</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1037"/>
<location filename="../Dialog/mainwindow.cpp" line="1346"/>
<location filename="../Dialog/mainwindow.cpp" line="956"/>
<location filename="../Dialog/mainwindow.cpp" line="1270"/>
<source>ChooseSaveFile</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1044"/>
<location filename="../Dialog/mainwindow.cpp" line="963"/>
<source>SaveAsGif</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1047"/>
<location filename="../Dialog/mainwindow.cpp" line="965"/>
<location filename="../Dialog/mainwindow.cpp" line="1102"/>
<source>Reprocessing</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="968"/>
<source>SaveAsSuccess</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1083"/>
<location filename="../Dialog/mainwindow.cpp" line="1004"/>
<source>ExportFrames</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1086"/>
<location filename="../Dialog/mainwindow.cpp" line="1355"/>
<location filename="../Dialog/mainwindow.cpp" line="1007"/>
<location filename="../Dialog/mainwindow.cpp" line="1279"/>
<source>ExportSuccess</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1089"/>
<location filename="../Dialog/mainwindow.cpp" line="1010"/>
<source>ExportFail</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1177"/>
<location filename="../Dialog/mainwindow.cpp" line="1100"/>
<source>SaveGif</source>
<translation> GIF </translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1181"/>
<location filename="../Dialog/mainwindow.cpp" line="1106"/>
<source>SaveSuccess</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1228"/>
<location filename="../Dialog/mainwindow.cpp" line="1154"/>
<source>DelayTime</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1228"/>
<location filename="../Dialog/mainwindow.cpp" line="1154"/>
<source>Inputms</source>
<translation> ms</translation>
</message>
<message>
<source>Input10ms</source>
<translation type="vanished"> 10ms</translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1247"/>
<location filename="../Dialog/mainwindow.cpp" line="1172"/>
<source>ScaleDelayTime</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1248"/>
<location filename="../Dialog/mainwindow.cpp" line="1173"/>
<source>InputPercent</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1267"/>
<location filename="../Dialog/mainwindow.cpp" line="1191"/>
<source>Images (*.jpg *.tiff *.png)</source>
<translation> (*.jpg *.tiff *.png)</translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1392"/>
<location filename="../Dialog/mainwindow.cpp" line="1316"/>
<source>NoSelection</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/mainwindow.cpp" line="1423"/>
<location filename="../Dialog/mainwindow.cpp" line="1347"/>
<source>InvalidModel</source>
<translation></translation>
</message>
@ -763,44 +753,6 @@
<source>A tiny easy2use gif editor for Deepin</source>
<translation> Deepin GIF </translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="4"/>
<source>Editor</source>
<translation></translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="5"/>
<source>Appearance</source>
<translation></translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="7"/>
<source>Image</source>
<translation></translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="8"/>
<source>Quality</source>
<translation> 1 - 30 </translation>
</message>
<message>
<source>Quantizer</source>
<translation type="vanished"></translation>
</message>
<message>
<source>Ditherer</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="10"/>
<source>Window</source>
<translation></translation>
</message>
<message>
<location filename="../settingdialog.cpp" line="11"/>
<source>Window size</source>
<translation></translation>
</message>
</context>
<context>
<name>ReduceFrameDialog</name>
@ -854,81 +806,41 @@
</message>
</context>
<context>
<name>Settings</name>
<name>SettingDialog</name>
<message>
<source>No</source>
<translation type="vanished"></translation>
<location filename="../Dialog/settingdialog.cpp" line="9"/>
<source>SettingDialog</source>
<translation></translation>
</message>
<message>
<source>M2</source>
<translation type="vanished">M2 </translation>
<location filename="../Dialog/settingdialog.cpp" line="10"/>
<source>WinState</source>
<translation></translation>
</message>
<message>
<source>Bayer</source>
<translation type="vanished">Bayer </translation>
</message>
<message>
<source>FloydSteinberg</source>
<translation type="vanished">FloydSteinberg </translation>
</message>
<message>
<source>Uniform</source>
<translation type="vanished"></translation>
</message>
<message>
<source>MedianCut</source>
<translation type="vanished"></translation>
</message>
<message>
<source>KMeans</source>
<translation type="vanished">K均值</translation>
</message>
<message>
<source>Random</source>
<translation type="vanished"></translation>
</message>
<message>
<source>Octree</source>
<translation type="vanished"></translation>
</message>
<message>
<source>NeuQuant</source>
<translation type="vanished"></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="37"/>
<location filename="../Dialog/settingdialog.cpp" line="13"/>
<source>Normal</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="37"/>
<location filename="../Dialog/settingdialog.cpp" line="13"/>
<source>Maximum</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="38"/>
<location filename="../Dialog/settingdialog.cpp" line="13"/>
<source>Minimum</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="38"/>
<source>Fullscreen</source>
<translation></translation>
<location filename="../Dialog/settingdialog.cpp" line="17"/>
<source>Terminal</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="90"/>
<source>Cancel</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="91"/>
<source>Replace</source>
<translation></translation>
</message>
<message>
<location filename="../Class/settings.cpp" line="93"/>
<source>OK</source>
<translation></translation>
<location filename="../Dialog/settingdialog.cpp" line="23"/>
<source>Reprocessing</source>
<translation> %1 </translation>
</message>
</context>
<context>
@ -947,12 +859,12 @@
<context>
<name>WaitingDialog</name>
<message>
<location filename="../Dialog/waitingdialog.cpp" line="10"/>
<location filename="../Dialog/waitingdialog.cpp" line="9"/>
<source>PleaseWait</source>
<translation></translation>
</message>
<message>
<location filename="../Dialog/waitingdialog.cpp" line="26"/>
<location filename="../Dialog/waitingdialog.cpp" line="25"/>
<source>Please Waiting...</source>
<translation></translation>
</message>

View File

@ -1,6 +1,7 @@
#include "Dialog/mainwindow.h"
#include <DApplication>
#include <DApplicationSettings>
#include <DLog>
#include <DMessageBox>
#include <DTitlebar>
#include <DWidgetUtil>
@ -9,6 +10,7 @@
#include <QTranslator>
DWIDGET_USE_NAMESPACE
DCORE_USE_NAMESPACE
int main(int argc, char *argv[]) {
//解决 root/ubuntu 主题样式走形
@ -42,7 +44,7 @@ int main(int argc, char *argv[]) {
a.setOrganizationName("WingCloud");
a.setApplicationName("WingGifEditor");
a.setApplicationVersion("1.1.1");
a.setApplicationVersion("1.1.2");
a.setProductIcon(QIcon(":/images/icon.png"));
a.setProductName(QObject::tr("WingGifEditor"));
a.setApplicationDescription(
@ -64,6 +66,9 @@ int main(int argc, char *argv[]) {
DApplicationSettings as;
Q_UNUSED(as)
DLogManager::registerFileAppender();
DLogManager::registerConsoleAppender();
MainWindow w;
if (filename.length() && w.checkIsGif(filename))

View File

@ -65,7 +65,6 @@
<file>images/readonly.png</file>
<file>images/rwg.png</file>
<file>images/fitinview.png</file>
<file>settings.json</file>
<file>images/setting.png</file>
<file>images/fullscreen.png</file>
</qresource>

View File

@ -1,12 +0,0 @@
#include <DSettings>
void GenerateSettingTranslate() {
auto group_editor = QObject::tr("Editor");
auto group_appearance = QObject::tr("Appearance");
auto editor_image = QObject::tr("Image");
auto image_quality = QObject::tr("Quality");
auto window = QObject::tr("Window");
auto app_windowstate = QObject::tr("Window size");
}

View File

@ -1,54 +0,0 @@
{
"groups": [
{
"key": "editor",
"name": "Editor",
"groups": [
{
"key": "image",
"name": "Image",
"options": [
{
"key": "quality",
"name": "Quality",
"type": "spinbutton",
"default": 10,
"max": 30,
"min": 1
}
]
}
]
},
{
"key": "appearance",
"name": "Appearance",
"groups": [
{
"key": "window",
"name": "Window",
"options": [
{
"key": "windowsize",
"name": "Window size",
"type": "combobox",
"default": "window_normal"
},
{
"key": "window_width",
"hide": true,
"reset": false,
"default": 0.6
},
{
"key": "window_height",
"hide": true,
"reset": false,
"default": 0.6
}
]
}
]
}
]
}

View File

@ -1,8 +1,18 @@
更新日志(由寂静的羽夏编写):
v1.0 beta:
1. 实现基本编辑功能
2. 实现洋葱皮功能 #I5J8WY
v1.1.2:
1. 更换编码库为 cgif
2. 增加图像后处理,充分利用现有的控制台图像处理工具
3. 修复切割图像窗体关闭后仍不恢复的 Bug
4. 增加日志系统
v1.1.1:
1. 使等待弹窗提示动画更加流畅
2. 增大神经网络参数,以解决部分 GIF 图片无法正常保存的问题
v1.1.0:
1. 修复已知问题,优化用户体验
2. 修复等待弹窗提示被卡死的 Bug
v1.0.0:
1. 修复从 GIF 创建应用闪退
@ -10,10 +20,6 @@ v1.0.0:
3. 修复部分已知 Bug
4. 更换 GIF 编码解析库,摆脱额外依赖
v1.1.0:
1. 修复已知问题,优化用户体验
2. 修复等待弹窗提示被卡死的 Bug
v1.1.1:
1. 使等待弹窗提示动画更加流畅
2. 增大神经网络参数,以解决部分 GIF 图片无法正常保存的问题
v1.0 beta:
1. 实现基本编辑功能
2. 实现洋葱皮功能 #I5J8WY