mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 23:42:57 +00:00
Add options to save GIF files (how to quantize palettes mainly)
* Added "context" parameter to save/load routines so we can test them.
This commit is contained in:
parent
b9a1fa8e17
commit
411ceda0e7
30
data/widgets/gif_options.xml
Normal file
30
data/widgets/gif_options.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!-- Aseprite -->
|
||||||
|
<!-- Copyright (C) 2014 by David Capello -->
|
||||||
|
<gui>
|
||||||
|
<window text="GIF Options" id="gif_options">
|
||||||
|
<vbox>
|
||||||
|
<vbox id="rgb_options">
|
||||||
|
<separator text="RGBA to Indexed Conversion:" left="true" horizontal="true" />
|
||||||
|
<check text="Enable Dithering" id="dither" />
|
||||||
|
|
||||||
|
<separator text="Optimize Color Palette:" left="true" horizontal="true" />
|
||||||
|
<radio id="quantize_each" text="&One palette for each frame" group="1" />
|
||||||
|
<radio id="quantize_all" text="&One palette for all frames" group="1" />
|
||||||
|
<radio id="no_quantize" text="&Don't modify palettes" group="1" />
|
||||||
|
</vbox>
|
||||||
|
|
||||||
|
<separator text="Optimize for Web:" left="true" horizontal="true" />
|
||||||
|
<check text="Interlaced" id="interlaced" />
|
||||||
|
|
||||||
|
<separator horizontal="true" />
|
||||||
|
|
||||||
|
<hbox>
|
||||||
|
<boxfiller />
|
||||||
|
<hbox homogeneous="true">
|
||||||
|
<button text="&OK" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||||
|
<button text="&Cancel" closewindow="true" />
|
||||||
|
</hbox>
|
||||||
|
</hbox>
|
||||||
|
</vbox>
|
||||||
|
</window>
|
||||||
|
</gui>
|
@ -214,7 +214,7 @@ int App::run()
|
|||||||
end = m_files.end();
|
end = m_files.end();
|
||||||
it != end; ++it) {
|
it != end; ++it) {
|
||||||
// Load the sprite
|
// Load the sprite
|
||||||
Document* document = load_document(it->c_str());
|
Document* document = load_document(context, it->c_str());
|
||||||
if (!document) {
|
if (!document) {
|
||||||
if (!isGui())
|
if (!isGui())
|
||||||
console.printf("Error loading file \"%s\"\n", it->c_str());
|
console.printf("Error loading file \"%s\"\n", it->c_str());
|
||||||
|
@ -126,7 +126,7 @@ void OpenFileCommand::onExecute(Context* context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!m_filename.empty()) {
|
if (!m_filename.empty()) {
|
||||||
base::UniquePtr<FileOp> fop(fop_to_load_document(m_filename.c_str(), FILE_LOAD_SEQUENCE_ASK));
|
base::UniquePtr<FileOp> fop(fop_to_load_document(context, m_filename.c_str(), FILE_LOAD_SEQUENCE_ASK));
|
||||||
bool unrecent = false;
|
bool unrecent = false;
|
||||||
|
|
||||||
if (fop) {
|
if (fop) {
|
||||||
|
@ -587,7 +587,8 @@ void PaletteEntryEditor::onQuantizeClick(Event& ev)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
palette = quantization::create_palette_from_rgb(sprite, reader.frame());
|
palette = quantization::create_palette_from_rgb(
|
||||||
|
sprite, reader.frame(), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNewPalette(palette, "Quantize Palette");
|
setNewPalette(palette, "Quantize Palette");
|
||||||
|
@ -81,9 +81,9 @@ private:
|
|||||||
FileOp* m_fop;
|
FileOp* m_fop;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void save_document_in_background(Document* document, bool mark_as_saved)
|
static void save_document_in_background(Context* context, Document* document, bool mark_as_saved)
|
||||||
{
|
{
|
||||||
base::UniquePtr<FileOp> fop(fop_to_save_document(document));
|
base::UniquePtr<FileOp> fop(fop_to_save_document(context, document));
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ protected:
|
|||||||
m_selectedFilename = filename;
|
m_selectedFilename = filename;
|
||||||
|
|
||||||
// Save the document
|
// Save the document
|
||||||
save_document_in_background(documentWriter, markAsSaved);
|
save_document_in_background(writer.context(), documentWriter, markAsSaved);
|
||||||
|
|
||||||
if (documentWriter->isModified())
|
if (documentWriter->isModified())
|
||||||
documentWriter->setFilename(oldFilename);
|
documentWriter->setFilename(oldFilename);
|
||||||
@ -251,7 +251,7 @@ void SaveFileCommand::onExecute(Context* context)
|
|||||||
if (!confirmReadonly(documentWriter->getFilename()))
|
if (!confirmReadonly(documentWriter->getFilename()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
save_document_in_background(documentWriter, true);
|
save_document_in_background(context, documentWriter, true);
|
||||||
update_screen_for_document(documentWriter);
|
update_screen_for_document(documentWriter);
|
||||||
}
|
}
|
||||||
// If the document isn't associated to a file, we must to show the
|
// If the document isn't associated to a file, we must to show the
|
||||||
|
@ -32,6 +32,11 @@
|
|||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
Context::Context()
|
||||||
|
: m_settings(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Context::Context(ISettings* settings)
|
Context::Context(ISettings* settings)
|
||||||
: m_settings(settings)
|
: m_settings(settings)
|
||||||
{
|
{
|
||||||
@ -159,4 +164,9 @@ void Context::onRemoveDocument(Document* document)
|
|||||||
m_observers.notifyRemoveDocument(this, document);
|
m_observers.notifyRemoveDocument(this, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Context::onGetActiveLocation(DocumentLocation* location) const
|
||||||
|
{
|
||||||
|
// Without active location
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
@ -44,6 +44,9 @@ namespace app {
|
|||||||
|
|
||||||
class Context {
|
class Context {
|
||||||
public:
|
public:
|
||||||
|
Context();
|
||||||
|
// The "settings" are deleted automatically in the ~Context destructor
|
||||||
|
Context(ISettings* settings);
|
||||||
virtual ~Context();
|
virtual ~Context();
|
||||||
|
|
||||||
virtual bool isUiAvailable() const { return false; }
|
virtual bool isUiAvailable() const { return false; }
|
||||||
@ -76,19 +79,11 @@ namespace app {
|
|||||||
void removeObserver(ContextObserver* observer);
|
void removeObserver(ContextObserver* observer);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
// The "settings" are deleted automatically in the ~Context destructor
|
|
||||||
Context(ISettings* settings);
|
|
||||||
|
|
||||||
virtual void onAddDocument(Document* document);
|
virtual void onAddDocument(Document* document);
|
||||||
virtual void onRemoveDocument(Document* document);
|
virtual void onRemoveDocument(Document* document);
|
||||||
virtual void onGetActiveLocation(DocumentLocation* location) const = 0;
|
virtual void onGetActiveLocation(DocumentLocation* location) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// Without default constructor
|
|
||||||
Context();
|
|
||||||
|
|
||||||
// List of all documents.
|
// List of all documents.
|
||||||
Documents m_documents;
|
Documents m_documents;
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ namespace app {
|
|||||||
// Loaded options from file
|
// Loaded options from file
|
||||||
|
|
||||||
void setFormatOptions(const SharedPtr<FormatOptions>& format_options);
|
void setFormatOptions(const SharedPtr<FormatOptions>& format_options);
|
||||||
|
SharedPtr<FormatOptions> getFormatOptions() { return m_format_options; }
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Boundaries
|
// Boundaries
|
||||||
|
@ -187,7 +187,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin
|
|||||||
}
|
}
|
||||||
|
|
||||||
new_image = quantization::convert_pixel_format
|
new_image = quantization::convert_pixel_format
|
||||||
(old_image, newFormat, dithering_method, rgbmap,
|
(old_image, NULL, newFormat, dithering_method, rgbmap,
|
||||||
sprite->getPalette(frame),
|
sprite->getPalette(frame),
|
||||||
is_image_from_background);
|
is_image_from_background);
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/document_api.h"
|
#include "app/document_api.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
|
#include "app/ui_context.h"
|
||||||
#include "base/compiler_specific.h"
|
#include "base/compiler_specific.h"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/unique_ptr.h"
|
#include "base/unique_ptr.h"
|
||||||
@ -178,7 +179,7 @@ void DocumentExporter::exportSheet()
|
|||||||
// Save the image files.
|
// Save the image files.
|
||||||
if (!m_textureFilename.empty()) {
|
if (!m_textureFilename.empty()) {
|
||||||
textureDocument->setFilename(m_textureFilename.c_str());
|
textureDocument->setFilename(m_textureFilename.c_str());
|
||||||
save_document(textureDocument.get());
|
save_document(UIContext::instance(), textureDocument.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
|
|
||||||
#include "app/app.h"
|
|
||||||
#include "app/console.h"
|
#include "app/console.h"
|
||||||
|
#include "app/context.h"
|
||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/file/file_format.h"
|
#include "app/file/file_format.h"
|
||||||
#include "app/file/file_formats_manager.h"
|
#include "app/file/file_formats_manager.h"
|
||||||
@ -48,7 +48,7 @@ namespace app {
|
|||||||
|
|
||||||
using namespace base;
|
using namespace base;
|
||||||
|
|
||||||
static FileOp* fop_new(FileOpType type);
|
static FileOp* fop_new(FileOpType type, Context* context);
|
||||||
static void fop_prepare_for_sequence(FileOp* fop);
|
static void fop_prepare_for_sequence(FileOp* fop);
|
||||||
|
|
||||||
static FileFormat* get_fileformat(const char* extension);
|
static FileFormat* get_fileformat(const char* extension);
|
||||||
@ -90,12 +90,12 @@ void get_writable_extensions(char* buf, int size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Document* load_document(const char* filename)
|
Document* load_document(Context* context, const char* filename)
|
||||||
{
|
{
|
||||||
Document* document;
|
Document* document;
|
||||||
|
|
||||||
/* TODO add a option to configure what to do with the sequence */
|
/* TODO add a option to configure what to do with the sequence */
|
||||||
FileOp *fop = fop_to_load_document(filename, FILE_LOAD_SEQUENCE_NONE);
|
FileOp *fop = fop_to_load_document(context, filename, FILE_LOAD_SEQUENCE_NONE);
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -116,10 +116,10 @@ Document* load_document(const char* filename)
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
int save_document(Document* document)
|
int save_document(Context* context, Document* document)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
FileOp* fop = fop_to_save_document(document);
|
FileOp* fop = fop_to_save_document(context, document);
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -138,11 +138,11 @@ int save_document(Document* document)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOp* fop_to_load_document(const char* filename, int flags)
|
FileOp* fop_to_load_document(Context* context, const char* filename, int flags)
|
||||||
{
|
{
|
||||||
FileOp *fop;
|
FileOp *fop;
|
||||||
|
|
||||||
fop = fop_new(FileOpLoad);
|
fop = fop_new(FileOpLoad, context);
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -199,8 +199,7 @@ FileOp* fop_to_load_document(const char* filename, int flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* TODO add a better dialog to edit file-names */
|
/* TODO add a better dialog to edit file-names */
|
||||||
if ((flags & FILE_LOAD_SEQUENCE_ASK) &&
|
if ((flags & FILE_LOAD_SEQUENCE_ASK) && context->isUiAvailable()) {
|
||||||
App::instance()->isGui()) {
|
|
||||||
/* really want load all files? */
|
/* really want load all files? */
|
||||||
if ((fop->seq.filename_list.size() > 1) &&
|
if ((fop->seq.filename_list.size() > 1) &&
|
||||||
(ui::Alert::show("Notice"
|
(ui::Alert::show("Notice"
|
||||||
@ -231,12 +230,12 @@ done:;
|
|||||||
return fop;
|
return fop;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileOp* fop_to_save_document(Document* document)
|
FileOp* fop_to_save_document(Context* context, Document* document)
|
||||||
{
|
{
|
||||||
FileOp *fop;
|
FileOp *fop;
|
||||||
bool fatal;
|
bool fatal;
|
||||||
|
|
||||||
fop = fop_new(FileOpSave);
|
fop = fop_new(FileOpSave, context);
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -323,7 +322,7 @@ FileOp* fop_to_save_document(Document* document)
|
|||||||
// Show the confirmation alert
|
// Show the confirmation alert
|
||||||
if (!warnings.empty()) {
|
if (!warnings.empty()) {
|
||||||
// Interative
|
// Interative
|
||||||
if (App::instance()->isGui()) {
|
if (context->isUiAvailable()) {
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (fatal)
|
if (fatal)
|
||||||
@ -663,8 +662,9 @@ void fop_post_load(FileOp* fop)
|
|||||||
fop->document->getSprite()->getPalettes().size() <= 1 &&
|
fop->document->getSprite()->getPalettes().size() <= 1 &&
|
||||||
fop->document->getSprite()->getPalette(FrameNumber(0))->isBlack()) {
|
fop->document->getSprite()->getPalette(FrameNumber(0))->isBlack()) {
|
||||||
SharedPtr<Palette> palette
|
SharedPtr<Palette> palette
|
||||||
(quantization::create_palette_from_rgb(fop->document->getSprite(),
|
(quantization::create_palette_from_rgb(
|
||||||
FrameNumber(0)));
|
fop->document->getSprite(),
|
||||||
|
FrameNumber(0), NULL));
|
||||||
|
|
||||||
fop->document->getSprite()->resetPalettes();
|
fop->document->getSprite()->resetPalettes();
|
||||||
fop->document->getSprite()->setPalette(palette, false);
|
fop->document->getSprite()->setPalette(palette, false);
|
||||||
@ -802,13 +802,14 @@ bool fop_is_stop(FileOp *fop)
|
|||||||
return stop;
|
return stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FileOp* fop_new(FileOpType type)
|
static FileOp* fop_new(FileOpType type, Context* context)
|
||||||
{
|
{
|
||||||
FileOp* fop = new FileOp;
|
FileOp* fop = new FileOp;
|
||||||
|
|
||||||
fop->type = type;
|
fop->type = type;
|
||||||
fop->format = NULL;
|
fop->format = NULL;
|
||||||
fop->format_data = NULL;
|
fop->format_data = NULL;
|
||||||
|
fop->context = context;
|
||||||
fop->document = NULL;
|
fop->document = NULL;
|
||||||
|
|
||||||
fop->mutex = new base::mutex();
|
fop->mutex = new base::mutex();
|
||||||
|
@ -46,6 +46,7 @@ namespace raster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
class Context;
|
||||||
class Document;
|
class Document;
|
||||||
class FileFormat;
|
class FileFormat;
|
||||||
class FormatOptions;
|
class FormatOptions;
|
||||||
@ -70,6 +71,7 @@ namespace app {
|
|||||||
FileOpType type; // Operation type: 0=load, 1=save.
|
FileOpType type; // Operation type: 0=load, 1=save.
|
||||||
FileFormat* format;
|
FileFormat* format;
|
||||||
void* format_data; // Custom data for the FileFormat::onLoad/onSave operations.
|
void* format_data; // Custom data for the FileFormat::onLoad/onSave operations.
|
||||||
|
Context* context;
|
||||||
Document* document; // Loaded document, or document to be saved.
|
Document* document; // Loaded document, or document to be saved.
|
||||||
std::string filename; // File-name to load/save.
|
std::string filename; // File-name to load/save.
|
||||||
|
|
||||||
@ -119,13 +121,13 @@ namespace app {
|
|||||||
|
|
||||||
// High-level routines to load/save documents.
|
// High-level routines to load/save documents.
|
||||||
|
|
||||||
Document* load_document(const char* filename);
|
Document* load_document(Context* context, const char* filename);
|
||||||
int save_document(Document* document);
|
int save_document(Context* context, Document* document);
|
||||||
|
|
||||||
// Low-level routines to load/save documents.
|
// Low-level routines to load/save documents.
|
||||||
|
|
||||||
FileOp* fop_to_load_document(const char* filename, int flags);
|
FileOp* fop_to_load_document(Context* context, const char* filename, int flags);
|
||||||
FileOp* fop_to_save_document(Document* document);
|
FileOp* fop_to_save_document(Context* context, Document* document);
|
||||||
void fop_operate(FileOp* fop, IFileOpProgress* progress);
|
void fop_operate(FileOp* fop, IFileOpProgress* progress);
|
||||||
void fop_done(FileOp* fop);
|
void fop_done(FileOp* fop);
|
||||||
void fop_stop(FileOp* fop);
|
void fop_stop(FileOp* fop);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "tests/test.h"
|
#include "tests/test.h"
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
|
#include "app/context.h"
|
||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
#include "app/file/file_formats_manager.h"
|
#include "app/file/file_formats_manager.h"
|
||||||
@ -37,6 +38,7 @@ TEST(File, SeveralSizes)
|
|||||||
// Register all possible image formats.
|
// Register all possible image formats.
|
||||||
FileFormatsManager::instance().registerAllFormats();
|
FileFormatsManager::instance().registerAllFormats();
|
||||||
std::vector<char> fn(256);
|
std::vector<char> fn(256);
|
||||||
|
app::Context context;
|
||||||
|
|
||||||
for (int w=10; w<=10+503*2; w+=503) {
|
for (int w=10; w<=10+503*2; w+=503) {
|
||||||
for (int h=10; h<=10+503*2; h+=503) {
|
for (int h=10; h<=10+503*2; h+=503) {
|
||||||
@ -61,11 +63,11 @@ TEST(File, SeveralSizes)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
save_document(doc);
|
save_document(&context, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
base::UniquePtr<Document> doc(load_document(&fn[0]));
|
base::UniquePtr<Document> doc(load_document(&context, &fn[0]));
|
||||||
ASSERT_EQ(w, doc->getSprite()->getWidth());
|
ASSERT_EQ(w, doc->getSprite()->getWidth());
|
||||||
ASSERT_EQ(h, doc->getSprite()->getHeight());
|
ASSERT_EQ(h, doc->getSprite()->getHeight());
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* Aseprite
|
/* Aseprite
|
||||||
* Copyright (C) 2001-2013 David Capello
|
* Copyright (C) 2001-2014 David Capello
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,16 +20,23 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "app/console.h"
|
||||||
|
#include "app/context.h"
|
||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
#include "app/file/file_format.h"
|
#include "app/file/file_format.h"
|
||||||
#include "app/file/format_options.h"
|
#include "app/file/format_options.h"
|
||||||
|
#include "app/file/gif_options.h"
|
||||||
|
#include "app/ini_file.h"
|
||||||
#include "app/modules/gui.h"
|
#include "app/modules/gui.h"
|
||||||
#include "app/util/autocrop.h"
|
#include "app/util/autocrop.h"
|
||||||
#include "base/file_handle.h"
|
#include "base/file_handle.h"
|
||||||
#include "base/unique_ptr.h"
|
#include "base/unique_ptr.h"
|
||||||
#include "raster/raster.h"
|
#include "raster/raster.h"
|
||||||
#include "ui/alert.h"
|
#include "ui/alert.h"
|
||||||
|
#include "ui/button.h"
|
||||||
|
|
||||||
|
#include "generated_gif_options.h"
|
||||||
|
|
||||||
#include <gif_lib.h>
|
#include <gif_lib.h>
|
||||||
|
|
||||||
@ -72,6 +79,7 @@ struct GifData
|
|||||||
};
|
};
|
||||||
|
|
||||||
class GifFormat : public FileFormat {
|
class GifFormat : public FileFormat {
|
||||||
|
|
||||||
const char* onGetName() const { return "gif"; }
|
const char* onGetName() const { return "gif"; }
|
||||||
const char* onGetExtensions() const { return "gif"; }
|
const char* onGetExtensions() const { return "gif"; }
|
||||||
int onGetFlags() const {
|
int onGetFlags() const {
|
||||||
@ -84,7 +92,8 @@ class GifFormat : public FileFormat {
|
|||||||
FILE_SUPPORT_GRAYA |
|
FILE_SUPPORT_GRAYA |
|
||||||
FILE_SUPPORT_INDEXED |
|
FILE_SUPPORT_INDEXED |
|
||||||
FILE_SUPPORT_FRAMES |
|
FILE_SUPPORT_FRAMES |
|
||||||
FILE_SUPPORT_PALETTES;
|
FILE_SUPPORT_PALETTES |
|
||||||
|
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop);
|
bool onLoad(FileOp* fop);
|
||||||
@ -93,6 +102,7 @@ class GifFormat : public FileFormat {
|
|||||||
#ifdef ENABLE_SAVE
|
#ifdef ENABLE_SAVE
|
||||||
bool onSave(FileOp* fop) OVERRIDE;
|
bool onSave(FileOp* fop) OVERRIDE;
|
||||||
#endif
|
#endif
|
||||||
|
SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) OVERRIDE;
|
||||||
};
|
};
|
||||||
|
|
||||||
FileFormat* CreateGifFormat()
|
FileFormat* CreateGifFormat()
|
||||||
@ -157,7 +167,8 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
ColorMapObject* colormap = gif_file->SColorMap;
|
ColorMapObject* colormap = gif_file->SColorMap;
|
||||||
for (int i=0; i<colormap->ColorCount; ++i) {
|
for (int i=0; i<colormap->ColorCount; ++i) {
|
||||||
current_palette->setEntry(i,
|
current_palette->setEntry(i,
|
||||||
rgba(colormap->Colors[i].Red,
|
rgba(
|
||||||
|
colormap->Colors[i].Red,
|
||||||
colormap->Colors[i].Green,
|
colormap->Colors[i].Green,
|
||||||
colormap->Colors[i].Blue, 255));
|
colormap->Colors[i].Blue, 255));
|
||||||
}
|
}
|
||||||
@ -208,9 +219,11 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
if (gif_file->Image.ColorMap) {
|
if (gif_file->Image.ColorMap) {
|
||||||
ColorMapObject* colormap = gif_file->Image.ColorMap;
|
ColorMapObject* colormap = gif_file->Image.ColorMap;
|
||||||
for (int i=0; i<colormap->ColorCount; ++i) {
|
for (int i=0; i<colormap->ColorCount; ++i) {
|
||||||
current_palette->setEntry(i, rgba(colormap->Colors[i].Red,
|
current_palette->setEntry(i,
|
||||||
colormap->Colors[i].Green,
|
rgba(
|
||||||
colormap->Colors[i].Blue, 255));
|
colormap->Colors[i].Red,
|
||||||
|
colormap->Colors[i].Green,
|
||||||
|
colormap->Colors[i].Blue, 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +262,7 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
data->frames[frame_num].disposal_method = disposal_method;
|
data->frames[frame_num].disposal_method = disposal_method;
|
||||||
data->frames[frame_num].mask_index = transparent_index;
|
data->frames[frame_num].mask_index = transparent_index;
|
||||||
|
|
||||||
PRINTF("Frame[%d] transparent index = %d\n", (int)frame_num, transparent_index);
|
// PRINTF("Frame[%d] transparent index = %d\n", (int)frame_num, transparent_index);
|
||||||
|
|
||||||
++frame_num;
|
++frame_num;
|
||||||
|
|
||||||
@ -272,8 +285,8 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
transparent_index = (extension[1] & 1) ? extension[4]: -1;
|
transparent_index = (extension[1] & 1) ? extension[4]: -1;
|
||||||
frame_delay = (extension[3] << 8) | extension[2];
|
frame_delay = (extension[3] << 8) | extension[2];
|
||||||
|
|
||||||
TRACE("Disposal method: %d\nTransparent index: %d\nFrame delay: %d\n",
|
// PRINTF("Disposal method: %d\nTransparent index: %d\nFrame delay: %d\n",
|
||||||
disposal_method, transparent_index, frame_delay);
|
// disposal_method, transparent_index, frame_delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +368,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
|
|||||||
ui::Alert::show("GIF Conversion"
|
ui::Alert::show("GIF Conversion"
|
||||||
"<<The selected file: %s"
|
"<<The selected file: %s"
|
||||||
"<<is a transparent GIF image which uses multiple background colors."
|
"<<is a transparent GIF image which uses multiple background colors."
|
||||||
"<<ASEPRITE cannot handle this kind of GIF correctly in Indexed format."
|
"<<" PACKAGE " cannot handle this kind of GIF correctly in Indexed format."
|
||||||
"<<What would you like to do?"
|
"<<What would you like to do?"
|
||||||
"||Convert to &RGBA||Keep &Indexed||&Cancel",
|
"||Convert to &RGBA||Keep &Indexed||&Cancel",
|
||||||
fop->document->getFilename().c_str());
|
fop->document->getFilename().c_str());
|
||||||
@ -367,6 +380,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the sprite with the GIF dimension
|
// Create the sprite with the GIF dimension
|
||||||
|
// TODO instead of 256 use the number of colors from the document
|
||||||
UniquePtr<Sprite> sprite(new Sprite(pixelFormat, data->sprite_w, data->sprite_h, 256));
|
UniquePtr<Sprite> sprite(new Sprite(pixelFormat, data->sprite_w, data->sprite_h, 256));
|
||||||
|
|
||||||
// Create the main layer
|
// Create the main layer
|
||||||
@ -524,26 +538,58 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
if (!gif_file)
|
if (!gif_file)
|
||||||
throw Exception("Error creating GIF file.\n");
|
throw Exception("Error creating GIF file.\n");
|
||||||
|
|
||||||
|
SharedPtr<GifOptions> gif_options = fop->seq.format_options;
|
||||||
Sprite* sprite = fop->document->getSprite();
|
Sprite* sprite = fop->document->getSprite();
|
||||||
int sprite_w = sprite->getWidth();
|
int sprite_w = sprite->getWidth();
|
||||||
int sprite_h = sprite->getHeight();
|
int sprite_h = sprite->getHeight();
|
||||||
PixelFormat sprite_format = sprite->getPixelFormat();
|
PixelFormat sprite_format = sprite->getPixelFormat();
|
||||||
bool interlace = false;
|
bool interlaced = gif_options->interlaced();
|
||||||
int loop = 0;
|
int loop = 0;
|
||||||
|
bool has_background = (sprite->getBackgroundLayer() ? true: false);
|
||||||
int background_color = (sprite_format == IMAGE_INDEXED ? sprite->getTransparentColor(): 0);
|
int background_color = (sprite_format == IMAGE_INDEXED ? sprite->getTransparentColor(): 0);
|
||||||
int transparent_index = (sprite->getBackgroundLayer() ? -1: sprite->getTransparentColor());
|
int transparent_index = (has_background ? -1: sprite->getTransparentColor());
|
||||||
|
|
||||||
Palette* current_palette = sprite->getPalette(FrameNumber(0));
|
Palette current_palette = *sprite->getPalette(FrameNumber(0));
|
||||||
Palette* previous_palette = current_palette;
|
Palette previous_palette(current_palette);
|
||||||
ColorMapObject* color_map = GifMakeMapObject(current_palette->size(), NULL);
|
RgbMap rgbmap;
|
||||||
for (int i = 0; i < current_palette->size(); ++i) {
|
|
||||||
color_map->Colors[i].Red = rgba_getr(current_palette->getEntry(i));
|
// The color map must be a power of two.
|
||||||
color_map->Colors[i].Green = rgba_getg(current_palette->getEntry(i));
|
int color_map_size = current_palette.size();
|
||||||
color_map->Colors[i].Blue = rgba_getb(current_palette->getEntry(i));
|
for (int i = 30; i >= 0; --i) {
|
||||||
|
if (color_map_size & (1 << i)) {
|
||||||
|
color_map_size = (1 << (i + (color_map_size & (1 << (i - 1)) ? 1: 0)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ASSERT(color_map_size > 0 && color_map_size <= 256);
|
||||||
|
|
||||||
if (EGifPutScreenDesc(gif_file, sprite_w, sprite_h,
|
ColorMapObject* color_map = NULL;
|
||||||
color_map->BitsPerPixel,
|
int bpp;
|
||||||
|
|
||||||
|
// We use a global color map only if this is a transparent GIF
|
||||||
|
if (!has_background) {
|
||||||
|
color_map = GifMakeMapObject(color_map_size, NULL);
|
||||||
|
if (color_map == NULL)
|
||||||
|
throw std::bad_alloc();
|
||||||
|
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < current_palette.size(); ++i) {
|
||||||
|
color_map->Colors[i].Red = rgba_getr(current_palette.getEntry(i));
|
||||||
|
color_map->Colors[i].Green = rgba_getg(current_palette.getEntry(i));
|
||||||
|
color_map->Colors[i].Blue = rgba_getb(current_palette.getEntry(i));
|
||||||
|
}
|
||||||
|
for (; i < color_map_size; ++i) {
|
||||||
|
color_map->Colors[i].Red = 0;
|
||||||
|
color_map->Colors[i].Green = 0;
|
||||||
|
color_map->Colors[i].Blue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bpp = color_map->BitsPerPixel;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
bpp = 8;
|
||||||
|
|
||||||
|
if (EGifPutScreenDesc(gif_file, sprite_w, sprite_h, bpp,
|
||||||
background_color, color_map) == GIF_ERROR)
|
background_color, color_map) == GIF_ERROR)
|
||||||
throw Exception("Error writing GIF header.\n");
|
throw Exception("Error writing GIF header.\n");
|
||||||
|
|
||||||
@ -562,44 +608,40 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
clear_image(current_image, background_color);
|
clear_image(current_image, background_color);
|
||||||
clear_image(previous_image, background_color);
|
clear_image(previous_image, background_color);
|
||||||
|
|
||||||
for (FrameNumber frame_num(0); frame_num<sprite->getTotalFrames(); ++frame_num) {
|
ColorMapObject* image_color_map = NULL;
|
||||||
current_palette = sprite->getPalette(frame_num);
|
|
||||||
|
|
||||||
|
for (FrameNumber frame_num(0); frame_num<sprite->getTotalFrames(); ++frame_num) {
|
||||||
// If the sprite is RGB or Grayscale, we must to convert it to Indexed on the fly.
|
// If the sprite is RGB or Grayscale, we must to convert it to Indexed on the fly.
|
||||||
if (sprite_format != IMAGE_INDEXED) {
|
if (sprite_format != IMAGE_INDEXED) {
|
||||||
clear_image(buffer_image, 0);
|
clear_image(buffer_image, background_color);
|
||||||
layer_render(sprite->getFolder(), buffer_image, 0, 0, frame_num);
|
layer_render(sprite->getFolder(), buffer_image, 0, 0, frame_num);
|
||||||
|
|
||||||
switch (sprite_format) {
|
switch (gif_options->quantize()) {
|
||||||
|
case GifOptions::NoQuantize:
|
||||||
// Convert the RGB image to Indexed
|
sprite->getPalette(frame_num)->copyColorsTo(¤t_palette);
|
||||||
case IMAGE_RGB:
|
|
||||||
for (int y = 0; y < sprite_h; ++y)
|
|
||||||
for (int x = 0; x < sprite_w; ++x) {
|
|
||||||
uint32_t pixel_value = get_pixel_fast<RgbTraits>(buffer_image, x, y);
|
|
||||||
put_pixel_fast<IndexedTraits>(current_image, x, y,
|
|
||||||
(rgba_geta(pixel_value) >= 128) ?
|
|
||||||
current_palette->findBestfit(rgba_getr(pixel_value),
|
|
||||||
rgba_getg(pixel_value),
|
|
||||||
rgba_getb(pixel_value)):
|
|
||||||
transparent_index);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
case GifOptions::QuantizeEach:
|
||||||
|
case GifOptions::QuantizeAll:
|
||||||
|
{
|
||||||
|
current_palette.makeBlack();
|
||||||
|
|
||||||
// Convert the Grayscale image to Indexed
|
std::vector<Image*> imgarray(1);
|
||||||
case IMAGE_GRAYSCALE:
|
imgarray[0] = buffer_image;
|
||||||
for (int y = 0; y < sprite_h; ++y)
|
raster::quantization::create_palette_from_images(imgarray, ¤t_palette, has_background);
|
||||||
for (int x = 0; x < sprite_w; ++x) {
|
}
|
||||||
uint16_t pixel_value = get_pixel_fast<GrayscaleTraits>(buffer_image, x, y);
|
|
||||||
put_pixel_fast<IndexedTraits>(current_image, x, y,
|
|
||||||
(graya_geta(pixel_value) >= 128) ?
|
|
||||||
current_palette->findBestfit(graya_getv(pixel_value),
|
|
||||||
graya_getv(pixel_value),
|
|
||||||
graya_getv(pixel_value)):
|
|
||||||
transparent_index);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rgbmap.regenerate(¤t_palette, transparent_index);
|
||||||
|
|
||||||
|
quantization::convert_pixel_format(
|
||||||
|
buffer_image,
|
||||||
|
current_image,
|
||||||
|
IMAGE_INDEXED,
|
||||||
|
gif_options->dithering(),
|
||||||
|
&rgbmap,
|
||||||
|
¤t_palette,
|
||||||
|
has_background);
|
||||||
}
|
}
|
||||||
// If the sprite is Indexed, we can render directly into "current_image".
|
// If the sprite is Indexed, we can render directly into "current_image".
|
||||||
else {
|
else {
|
||||||
@ -679,26 +721,32 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Image color map
|
// Image color map
|
||||||
ColorMapObject* image_color_map = NULL;
|
if ((!color_map && frame_num == 0) ||
|
||||||
if (current_palette != previous_palette) {
|
(current_palette.countDiff(&previous_palette, NULL, NULL) > 0)) {
|
||||||
image_color_map = GifMakeMapObject(current_palette->size(), NULL);
|
if (!image_color_map) {
|
||||||
for (int i = 0; i < current_palette->size(); ++i) {
|
image_color_map = GifMakeMapObject(current_palette.size(), NULL);
|
||||||
image_color_map->Colors[i].Red = rgba_getr(current_palette->getEntry(i));
|
if (image_color_map == NULL)
|
||||||
image_color_map->Colors[i].Green = rgba_getg(current_palette->getEntry(i));
|
throw std::bad_alloc();
|
||||||
image_color_map->Colors[i].Blue = rgba_getb(current_palette->getEntry(i));
|
|
||||||
}
|
}
|
||||||
previous_palette = current_palette;
|
|
||||||
|
for (int i = 0; i < current_palette.size(); ++i) {
|
||||||
|
image_color_map->Colors[i].Red = rgba_getr(current_palette.getEntry(i));
|
||||||
|
image_color_map->Colors[i].Green = rgba_getg(current_palette.getEntry(i));
|
||||||
|
image_color_map->Colors[i].Blue = rgba_getb(current_palette.getEntry(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
current_palette.copyColorsTo(&previous_palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the image record.
|
// Write the image record.
|
||||||
if (EGifPutImageDesc(gif_file,
|
if (EGifPutImageDesc(gif_file,
|
||||||
frame_x, frame_y,
|
frame_x, frame_y,
|
||||||
frame_w, frame_h, interlace ? 1: 0,
|
frame_w, frame_h, interlaced ? 1: 0,
|
||||||
image_color_map) == GIF_ERROR)
|
image_color_map) == GIF_ERROR)
|
||||||
throw Exception("Error writing GIF frame %d.\n", (int)frame_num);
|
throw Exception("Error writing GIF frame %d.\n", (int)frame_num);
|
||||||
|
|
||||||
// Write the image data (pixels).
|
// Write the image data (pixels).
|
||||||
if (interlace) {
|
if (interlaced) {
|
||||||
// Need to perform 4 passes on the images.
|
// Need to perform 4 passes on the images.
|
||||||
for (int i=0; i<4; ++i)
|
for (int i=0; i<4; ++i)
|
||||||
for (int y = interlaced_offset[i]; y < frame_h; y += interlaced_jumps[i]) {
|
for (int y = interlaced_offset[i]; y < frame_h; y += interlaced_jumps[i]) {
|
||||||
@ -711,7 +759,7 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Write all image scanlines (not interlaced in this case).
|
// Write all image scanlines (not interlaced in this case).
|
||||||
for (int y = 0; y < frame_h; ++y) {
|
for (int y=0; y<frame_h; ++y) {
|
||||||
IndexedTraits::address_t addr =
|
IndexedTraits::address_t addr =
|
||||||
(IndexedTraits::address_t)current_image->getPixelAddress(frame_x, frame_y + y);
|
(IndexedTraits::address_t)current_image->getPixelAddress(frame_x, frame_y + y);
|
||||||
|
|
||||||
@ -727,4 +775,69 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
SharedPtr<FormatOptions> GifFormat::onGetFormatOptions(FileOp* fop)
|
||||||
|
{
|
||||||
|
SharedPtr<GifOptions> gif_options;
|
||||||
|
if (fop->document->getFormatOptions() != NULL)
|
||||||
|
gif_options = SharedPtr<GifOptions>(fop->document->getFormatOptions());
|
||||||
|
|
||||||
|
if (!gif_options)
|
||||||
|
gif_options.reset(new GifOptions);
|
||||||
|
|
||||||
|
// Non-interactive mode
|
||||||
|
if (!fop->context->isUiAvailable())
|
||||||
|
return gif_options;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Configuration parameters
|
||||||
|
gif_options->setQuantize((GifOptions::Quantize)get_config_int("GIF", "Quantize", (int)gif_options->quantize()));
|
||||||
|
gif_options->setInterlaced(get_config_bool("GIF", "Interlaced", gif_options->interlaced()));
|
||||||
|
gif_options->setDithering((raster::DitheringMethod)get_config_int("GIF", "Dither", (int)gif_options->dithering()));
|
||||||
|
|
||||||
|
// Load the window to ask to the user the GIF options he wants.
|
||||||
|
|
||||||
|
app::gen::GifOptions win;
|
||||||
|
win.rgbOptions()->setVisible(fop->document->getSprite()->getPixelFormat() != IMAGE_INDEXED);
|
||||||
|
|
||||||
|
switch (gif_options->quantize()) {
|
||||||
|
case GifOptions::NoQuantize: win.noQuantize()->setSelected(true); break;
|
||||||
|
case GifOptions::QuantizeEach: win.quantizeEach()->setSelected(true); break;
|
||||||
|
case GifOptions::QuantizeAll: win.quantizeAll()->setSelected(true); break;
|
||||||
|
}
|
||||||
|
win.interlaced()->setSelected(gif_options->interlaced());
|
||||||
|
|
||||||
|
win.dither()->setEnabled(true);
|
||||||
|
win.dither()->setSelected(gif_options->dithering() == raster::DITHERING_ORDERED);
|
||||||
|
|
||||||
|
win.openWindowInForeground();
|
||||||
|
|
||||||
|
if (win.getKiller() == win.ok()) {
|
||||||
|
if (win.quantizeAll()->isSelected())
|
||||||
|
gif_options->setQuantize(GifOptions::QuantizeAll);
|
||||||
|
else if (win.quantizeEach()->isSelected())
|
||||||
|
gif_options->setQuantize(GifOptions::QuantizeEach);
|
||||||
|
else if (win.noQuantize()->isSelected())
|
||||||
|
gif_options->setQuantize(GifOptions::NoQuantize);
|
||||||
|
|
||||||
|
gif_options->setInterlaced(win.interlaced()->isSelected());
|
||||||
|
gif_options->setDithering(win.dither()->isSelected() ?
|
||||||
|
raster::DITHERING_ORDERED:
|
||||||
|
raster::DITHERING_NONE);
|
||||||
|
|
||||||
|
set_config_int("GIF", "Quantize", gif_options->quantize());
|
||||||
|
set_config_bool("GIF", "Interlaced", gif_options->interlaced());
|
||||||
|
set_config_int("GIF", "Dither", gif_options->dithering());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gif_options.reset(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gif_options;
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
Console::showException(e);
|
||||||
|
return SharedPtr<GifOptions>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
58
src/app/file/gif_options.h
Normal file
58
src/app/file/gif_options.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/* Aseprite
|
||||||
|
* Copyright (C) 2001-2014 David Capello
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APP_FILE_GIF_OPTIONS_H_INCLUDED
|
||||||
|
#define APP_FILE_GIF_OPTIONS_H_INCLUDED
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "app/file/format_options.h"
|
||||||
|
#include "raster/dithering_method.h"
|
||||||
|
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
// Data for GIF files
|
||||||
|
class GifOptions : public FormatOptions {
|
||||||
|
public:
|
||||||
|
enum Quantize { NoQuantize, QuantizeEach, QuantizeAll };
|
||||||
|
|
||||||
|
GifOptions(
|
||||||
|
Quantize quantize = QuantizeEach,
|
||||||
|
bool interlaced = false,
|
||||||
|
DitheringMethod dithering = raster::DITHERING_NONE)
|
||||||
|
: m_quantize(quantize)
|
||||||
|
, m_interlaced(interlaced)
|
||||||
|
, m_dithering(dithering) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Quantize quantize() const { return m_quantize; }
|
||||||
|
bool interlaced() const { return m_interlaced; }
|
||||||
|
raster::DitheringMethod dithering() const { return m_dithering; }
|
||||||
|
|
||||||
|
void setQuantize(const Quantize quantize) { m_quantize = quantize; }
|
||||||
|
void setInterlaced(bool interlaced) { m_interlaced = interlaced; }
|
||||||
|
void setDithering(const raster::DitheringMethod dithering) { m_dithering = dithering; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Quantize m_quantize;
|
||||||
|
bool m_interlaced;
|
||||||
|
raster::DitheringMethod m_dithering;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
|
||||||
|
#endif
|
312
src/app/file/gif_tests.cpp
Normal file
312
src/app/file/gif_tests.cpp
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
/* Aseprite
|
||||||
|
* Copyright (C) 2014 David Capello
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tests/test.h"
|
||||||
|
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/document.h"
|
||||||
|
#include "app/file/file.h"
|
||||||
|
#include "app/file/file_formats_manager.h"
|
||||||
|
#include "app/file/gif_options.h"
|
||||||
|
#include "raster/raster.h"
|
||||||
|
#include "she/scoped_handle.h"
|
||||||
|
#include "she/system.h"
|
||||||
|
|
||||||
|
using namespace app;
|
||||||
|
|
||||||
|
typedef base::UniquePtr<Document> DocumentPtr;
|
||||||
|
|
||||||
|
class GifFormat : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
GifFormat() : m_system(she::create_system()) {
|
||||||
|
FileFormatsManager::instance().registerAllFormats();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
app::Context m_context;
|
||||||
|
she::ScopedHandle<she::System> m_system;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(GifFormat, Dimensions)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_INDEXED, 31, 29, 14));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
sprite->setTransparentColor(3);
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
clear_image(image, doc->getSprite()->getTransparentColor());
|
||||||
|
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
EXPECT_EQ(31, sprite->getWidth());
|
||||||
|
EXPECT_EQ(29, sprite->getHeight());
|
||||||
|
EXPECT_EQ(3, sprite->getTransparentColor());
|
||||||
|
// TODO instead of 256, this should be 16 as Gif files contains
|
||||||
|
// palettes that are power of two.
|
||||||
|
EXPECT_EQ(256, sprite->getPalette(FrameNumber(0))->size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GifFormat, OpaqueIndexed)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_INDEXED, 2, 2, 4));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
pal->setEntry(0, rgb(255, 255, 255));
|
||||||
|
pal->setEntry(1, rgb(255, 13, 254));
|
||||||
|
pal->setEntry(2, rgb(129, 255, 32));
|
||||||
|
pal->setEntry(3, rgb(0, 0, 255));
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
layer->setBackground(true);
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
image->putPixel(0, 0, 0);
|
||||||
|
image->putPixel(0, 1, 1);
|
||||||
|
image->putPixel(1, 0, 2);
|
||||||
|
image->putPixel(1, 1, 3);
|
||||||
|
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
EXPECT_TRUE(layer->isBackground());
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
EXPECT_EQ(rgb(255, 255, 255), pal->getEntry(0));
|
||||||
|
EXPECT_EQ(rgb(255, 13, 254), pal->getEntry(1));
|
||||||
|
EXPECT_EQ(rgb(129, 255, 32), pal->getEntry(2));
|
||||||
|
EXPECT_EQ(rgb(0, 0, 255), pal->getEntry(3));
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
EXPECT_EQ(0, sprite->getTransparentColor());
|
||||||
|
EXPECT_EQ(0, image->getPixel(0, 0));
|
||||||
|
EXPECT_EQ(1, image->getPixel(0, 1));
|
||||||
|
EXPECT_EQ(2, image->getPixel(1, 0));
|
||||||
|
EXPECT_EQ(3, image->getPixel(1, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GifFormat, TransparentIndexed)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_INDEXED, 2, 2, 4));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
pal->setEntry(0, rgb(255, 255, 255));
|
||||||
|
pal->setEntry(1, rgb(255, 13, 254));
|
||||||
|
pal->setEntry(2, rgb(129, 255, 32));
|
||||||
|
pal->setEntry(3, rgb(0, 0, 255));
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
image->putPixel(0, 0, 0);
|
||||||
|
image->putPixel(0, 1, 1);
|
||||||
|
image->putPixel(1, 0, 2);
|
||||||
|
image->putPixel(1, 1, 3);
|
||||||
|
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
EXPECT_FALSE(layer->isBackground());
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
EXPECT_EQ(rgb(255, 255, 255), pal->getEntry(0));
|
||||||
|
EXPECT_EQ(rgb(255, 13, 254), pal->getEntry(1));
|
||||||
|
EXPECT_EQ(rgb(129, 255, 32), pal->getEntry(2));
|
||||||
|
EXPECT_EQ(rgb(0, 0, 255), pal->getEntry(3));
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
EXPECT_EQ(0, sprite->getTransparentColor());
|
||||||
|
EXPECT_EQ(0, image->getPixel(0, 0));
|
||||||
|
EXPECT_EQ(1, image->getPixel(0, 1));
|
||||||
|
EXPECT_EQ(2, image->getPixel(1, 0));
|
||||||
|
EXPECT_EQ(3, image->getPixel(1, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GifFormat, TransparentRgbQuantization)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_RGB, 2, 2, 256));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
image->putPixel(0, 0, rgba(0, 0, 0, 0));
|
||||||
|
image->putPixel(0, 1, rgb(255, 0, 0));
|
||||||
|
image->putPixel(1, 0, rgb(0, 255, 0));
|
||||||
|
image->putPixel(1, 1, rgb(0, 0, 255));
|
||||||
|
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
EXPECT_EQ(0, sprite->getTransparentColor());
|
||||||
|
EXPECT_EQ(0, image->getPixel(0, 0));
|
||||||
|
EXPECT_EQ(rgb(255, 0, 0), pal->getEntry(image->getPixel(0, 1)));
|
||||||
|
EXPECT_EQ(rgb(0, 255, 0), pal->getEntry(image->getPixel(1, 0)));
|
||||||
|
EXPECT_EQ(rgb(0, 0, 255), pal->getEntry(image->getPixel(1, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GifFormat, OpaqueRgbQuantization)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_RGB, 2, 2, 256));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
layer->setBackground(true);
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
EXPECT_NE((LayerImage*)NULL, sprite->getBackgroundLayer());
|
||||||
|
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
image->putPixel(0, 0, rgb(0, 0, 0));
|
||||||
|
image->putPixel(0, 1, rgb(255, 0, 0));
|
||||||
|
image->putPixel(1, 0, rgb(0, 255, 0));
|
||||||
|
image->putPixel(1, 1, rgb(0, 0, 255));
|
||||||
|
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
EXPECT_TRUE(layer->isBackground());
|
||||||
|
EXPECT_EQ(layer, sprite->getBackgroundLayer());
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
EXPECT_EQ(0, sprite->getTransparentColor());
|
||||||
|
EXPECT_EQ(rgb(0, 0, 0), pal->getEntry(image->getPixel(0, 0)));
|
||||||
|
EXPECT_EQ(rgb(255, 0, 0), pal->getEntry(image->getPixel(0, 1)));
|
||||||
|
EXPECT_EQ(rgb(0, 255, 0), pal->getEntry(image->getPixel(1, 0)));
|
||||||
|
EXPECT_EQ(rgb(0, 0, 255), pal->getEntry(image->getPixel(1, 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(GifFormat, OpaqueRgbQuantizationTwoLayers)
|
||||||
|
{
|
||||||
|
const char* fn = "test.gif";
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(Document::createBasicDocument(IMAGE_RGB, 2, 2, 256));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
doc->setFilename(fn);
|
||||||
|
|
||||||
|
LayerImage* layer1 = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
layer1->setBackground(true);
|
||||||
|
|
||||||
|
LayerImage* layer2 = new LayerImage(sprite);
|
||||||
|
sprite->getFolder()->addLayer(layer2);
|
||||||
|
|
||||||
|
Image* image1 = sprite->getStock()->getImage(layer1->getCel(FrameNumber(0))->getImage());
|
||||||
|
Image* image2 = Image::create(IMAGE_RGB, 2, 2);
|
||||||
|
int image2Idx = sprite->getStock()->addImage(image2);
|
||||||
|
Cel* cel2 = new Cel(FrameNumber(0), image2Idx);
|
||||||
|
layer2->addCel(cel2);
|
||||||
|
|
||||||
|
image1->clear(rgba(255, 255, 255, 255));
|
||||||
|
image2->putPixel(0, 0, rgba(255, 0, 0, 255));
|
||||||
|
image2->putPixel(1, 1, rgba(196, 0, 0, 255));
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
pal->setEntry(0, rgba(255, 255, 255, 255));
|
||||||
|
pal->setEntry(1, rgba(255, 0, 0, 255));
|
||||||
|
|
||||||
|
// Do not modify palettes
|
||||||
|
doc->setFormatOptions(SharedPtr<FormatOptions>(new GifOptions(GifOptions::NoQuantize)));
|
||||||
|
save_document(&m_context, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DocumentPtr doc(load_document(&m_context, fn));
|
||||||
|
Sprite* sprite = doc->getSprite();
|
||||||
|
|
||||||
|
LayerImage* layer = dynamic_cast<LayerImage*>(sprite->getFolder()->getFirstLayer());
|
||||||
|
ASSERT_NE((LayerImage*)NULL, layer);
|
||||||
|
ASSERT_TRUE(layer->isBackground());
|
||||||
|
|
||||||
|
Palette* pal = sprite->getPalette(FrameNumber(0));
|
||||||
|
Image* image = sprite->getStock()->getImage(layer->getCel(FrameNumber(0))->getImage());
|
||||||
|
EXPECT_EQ(0, sprite->getTransparentColor());
|
||||||
|
|
||||||
|
EXPECT_EQ(1, image->getPixel(0, 0));
|
||||||
|
EXPECT_EQ(0, image->getPixel(0, 1));
|
||||||
|
EXPECT_EQ(0, image->getPixel(1, 0));
|
||||||
|
EXPECT_EQ(1, image->getPixel(1, 1));
|
||||||
|
|
||||||
|
EXPECT_EQ(rgba(255, 255, 255, 255), pal->getEntry(0));
|
||||||
|
EXPECT_EQ(rgba(255, 0, 0, 255), pal->getEntry(1));
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
#include "app/console.h"
|
#include "app/console.h"
|
||||||
|
#include "app/context.h"
|
||||||
|
#include "app/document.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
#include "app/file/file_format.h"
|
#include "app/file/file_format.h"
|
||||||
#include "app/file/format_options.h"
|
#include "app/file/format_options.h"
|
||||||
@ -358,15 +360,21 @@ bool JpegFormat::onSave(FileOp* fop)
|
|||||||
// Shows the JPEG configuration dialog.
|
// Shows the JPEG configuration dialog.
|
||||||
SharedPtr<FormatOptions> JpegFormat::onGetFormatOptions(FileOp* fop)
|
SharedPtr<FormatOptions> JpegFormat::onGetFormatOptions(FileOp* fop)
|
||||||
{
|
{
|
||||||
SharedPtr<JpegOptions> jpeg_options(new JpegOptions());
|
SharedPtr<JpegOptions> jpeg_options;
|
||||||
|
if (fop->document->getFormatOptions() != NULL)
|
||||||
|
jpeg_options = SharedPtr<JpegOptions>(fop->document->getFormatOptions());
|
||||||
|
|
||||||
|
if (!jpeg_options)
|
||||||
|
jpeg_options.reset(new JpegOptions);
|
||||||
|
|
||||||
|
// Non-interactive mode
|
||||||
|
if (!fop->context->isUiAvailable())
|
||||||
|
return jpeg_options;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Configuration parameters
|
// Configuration parameters
|
||||||
jpeg_options->quality = get_config_float("JPEG", "Quality", 1.0f);
|
jpeg_options->quality = get_config_float("JPEG", "Quality", 1.0f);
|
||||||
|
|
||||||
// Interactive mode
|
|
||||||
if (!App::instance()->isGui())
|
|
||||||
return jpeg_options;
|
|
||||||
|
|
||||||
// Load the window to ask to the user the JPEG options he wants.
|
// Load the window to ask to the user the JPEG options he wants.
|
||||||
UniquePtr<ui::Window> window(app::load_widget<ui::Window>("jpeg_options.xml", "jpeg_options"));
|
UniquePtr<ui::Window> window(app::load_widget<ui::Window>("jpeg_options.xml", "jpeg_options"));
|
||||||
ui::Slider* slider_quality = app::find_widget<ui::Slider>(window, "quality");
|
ui::Slider* slider_quality = app::find_widget<ui::Slider>(window, "quality");
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "app/thumbnail_generator.h"
|
#include "app/thumbnail_generator.h"
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
|
#include "app/context.h"
|
||||||
#include "app/document.h"
|
#include "app/document.h"
|
||||||
#include "app/file/file.h"
|
#include "app/file/file.h"
|
||||||
#include "app/file_system.h"
|
#include "app/file_system.h"
|
||||||
@ -190,9 +191,12 @@ void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
|
|||||||
getWorkerStatus(fileitem, progress) != WithoutWorker)
|
getWorkerStatus(fileitem, progress) != WithoutWorker)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FileOp* fop = fop_to_load_document(fileitem->getFileName().c_str(),
|
Context tmpContext;
|
||||||
FILE_LOAD_SEQUENCE_NONE |
|
FileOp* fop = fop_to_load_document(&tmpContext,
|
||||||
FILE_LOAD_ONE_FRAME);
|
fileitem->getFileName().c_str(),
|
||||||
|
FILE_LOAD_SEQUENCE_NONE |
|
||||||
|
FILE_LOAD_ONE_FRAME);
|
||||||
|
|
||||||
if (!fop)
|
if (!fop)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ void clipboard::paste()
|
|||||||
RgbMap* dst_rgbmap = dst_sprite->getRgbMap(editor->getFrame());
|
RgbMap* dst_rgbmap = dst_sprite->getRgbMap(editor->getFrame());
|
||||||
|
|
||||||
src_image = quantization::convert_pixel_format(
|
src_image = quantization::convert_pixel_format(
|
||||||
clipboard_image, dst_sprite->getPixelFormat(),
|
clipboard_image, NULL, dst_sprite->getPixelFormat(),
|
||||||
DITHERING_NONE, dst_rgbmap, clipboard_palette,
|
DITHERING_NONE, dst_rgbmap, clipboard_palette,
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,10 @@ namespace raster {
|
|||||||
(a << rgba_a_shift));
|
(a << rgba_a_shift));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
return rgba(0, 0, 0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Grayscale
|
// Grayscale
|
||||||
|
|
||||||
@ -85,6 +89,10 @@ namespace raster {
|
|||||||
(a << graya_a_shift));
|
(a << graya_a_shift));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint16_t gray(uint8_t v) {
|
||||||
|
return graya(v, 255);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace raster
|
} // namespace raster
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -46,9 +46,9 @@ Palette::Palette(FrameNumber frame, int ncolors)
|
|||||||
|
|
||||||
m_frame = frame;
|
m_frame = frame;
|
||||||
m_colors.resize(ncolors);
|
m_colors.resize(ncolors);
|
||||||
m_modifications = 0;
|
|
||||||
|
|
||||||
std::fill(m_colors.begin(), m_colors.end(), rgba(0, 0, 0, 255));
|
makeBlack();
|
||||||
|
m_modifications = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Palette::Palette(const Palette& palette)
|
Palette::Palette(const Palette& palette)
|
||||||
|
@ -45,17 +45,22 @@ namespace quantization {
|
|||||||
using namespace gfx;
|
using namespace gfx;
|
||||||
|
|
||||||
// Converts a RGB image to indexed with ordered dithering method.
|
// Converts a RGB image to indexed with ordered dithering method.
|
||||||
static Image* ordered_dithering(const Image* src_image,
|
static Image* ordered_dithering(
|
||||||
int offsetx, int offsety,
|
const Image* src_image,
|
||||||
const RgbMap* rgbmap,
|
Image* dst_image,
|
||||||
const Palette* palette);
|
int offsetx, int offsety,
|
||||||
|
const RgbMap* rgbmap,
|
||||||
|
const Palette* palette);
|
||||||
|
|
||||||
static void create_palette_from_bitmaps(const std::vector<Image*>& images, Palette* palette, bool has_background_layer);
|
Palette* create_palette_from_rgb(
|
||||||
|
const Sprite* sprite,
|
||||||
Palette* create_palette_from_rgb(const Sprite* sprite, FrameNumber frameNumber)
|
FrameNumber frameNumber,
|
||||||
|
Palette* palette)
|
||||||
{
|
{
|
||||||
|
if (!palette)
|
||||||
|
palette = new Palette(FrameNumber(0), 256);
|
||||||
|
|
||||||
bool has_background_layer = (sprite->getBackgroundLayer() != NULL);
|
bool has_background_layer = (sprite->getBackgroundLayer() != NULL);
|
||||||
Palette* palette = new Palette(FrameNumber(0), 256);
|
|
||||||
Image* flat_image;
|
Image* flat_image;
|
||||||
|
|
||||||
ImagesCollector images(sprite->getFolder(), // All layers
|
ImagesCollector images(sprite->getFolder(), // All layers
|
||||||
@ -78,27 +83,31 @@ Palette* create_palette_from_rgb(const Sprite* sprite, FrameNumber frameNumber)
|
|||||||
image_array[c++] = flat_image; // The 'flat_image'
|
image_array[c++] = flat_image; // The 'flat_image'
|
||||||
|
|
||||||
// Generate an optimized palette for all images
|
// Generate an optimized palette for all images
|
||||||
create_palette_from_bitmaps(image_array, palette, has_background_layer);
|
create_palette_from_images(image_array, palette, has_background_layer);
|
||||||
|
|
||||||
delete flat_image;
|
delete flat_image;
|
||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image* convert_pixel_format(const Image* image,
|
Image* convert_pixel_format(
|
||||||
PixelFormat pixelFormat,
|
const Image* image,
|
||||||
DitheringMethod ditheringMethod,
|
Image* new_image,
|
||||||
const RgbMap* rgbmap,
|
PixelFormat pixelFormat,
|
||||||
const Palette* palette,
|
DitheringMethod ditheringMethod,
|
||||||
bool is_background_layer)
|
const RgbMap* rgbmap,
|
||||||
|
const Palette* palette,
|
||||||
|
bool is_background)
|
||||||
{
|
{
|
||||||
|
if (!new_image)
|
||||||
|
new_image = Image::create(pixelFormat, image->getWidth(), image->getHeight());
|
||||||
|
|
||||||
// RGB -> Indexed with ordered dithering
|
// RGB -> Indexed with ordered dithering
|
||||||
if (image->getPixelFormat() == IMAGE_RGB &&
|
if (image->getPixelFormat() == IMAGE_RGB &&
|
||||||
pixelFormat == IMAGE_INDEXED &&
|
pixelFormat == IMAGE_INDEXED &&
|
||||||
ditheringMethod == DITHERING_ORDERED) {
|
ditheringMethod == DITHERING_ORDERED) {
|
||||||
return ordered_dithering(image, 0, 0, rgbmap, palette);
|
return ordered_dithering(image, new_image, 0, 0, rgbmap, palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
Image* new_image = Image::create(pixelFormat, image->getWidth(), image->getHeight());
|
|
||||||
color_t c;
|
color_t c;
|
||||||
int r, g, b;
|
int r, g, b;
|
||||||
|
|
||||||
@ -223,7 +232,7 @@ Image* convert_pixel_format(const Image* image,
|
|||||||
ASSERT(dst_it != dst_end);
|
ASSERT(dst_it != dst_end);
|
||||||
c = *src_it;
|
c = *src_it;
|
||||||
|
|
||||||
if (!is_background_layer && c == image->getMaskColor())
|
if (!is_background && c == image->getMaskColor())
|
||||||
*dst_it = 0;
|
*dst_it = 0;
|
||||||
else
|
else
|
||||||
*dst_it = rgba(rgba_getr(palette->getEntry(c)),
|
*dst_it = rgba(rgba_getr(palette->getEntry(c)),
|
||||||
@ -243,7 +252,7 @@ Image* convert_pixel_format(const Image* image,
|
|||||||
ASSERT(dst_it != dst_end);
|
ASSERT(dst_it != dst_end);
|
||||||
c = *src_it;
|
c = *src_it;
|
||||||
|
|
||||||
if (!is_background_layer && c == image->getMaskColor())
|
if (!is_background && c == image->getMaskColor())
|
||||||
*dst_it = 0;
|
*dst_it = 0;
|
||||||
else {
|
else {
|
||||||
r = rgba_getr(palette->getEntry(c));
|
r = rgba_getr(palette->getEntry(c));
|
||||||
@ -268,7 +277,7 @@ Image* convert_pixel_format(const Image* image,
|
|||||||
ASSERT(dst_it != dst_end);
|
ASSERT(dst_it != dst_end);
|
||||||
c = *src_it;
|
c = *src_it;
|
||||||
|
|
||||||
if (!is_background_layer && c == image->getMaskColor())
|
if (!is_background && c == image->getMaskColor())
|
||||||
*dst_it = dstMaskColor;
|
*dst_it = dstMaskColor;
|
||||||
else {
|
else {
|
||||||
r = rgba_getr(palette->getEntry(c));
|
r = rgba_getr(palette->getEntry(c));
|
||||||
@ -321,13 +330,14 @@ static int pattern[8][8] = {
|
|||||||
4 * ((g1)-(g2)) * ((g1)-(g2)) + \
|
4 * ((g1)-(g2)) * ((g1)-(g2)) + \
|
||||||
2 * ((b1)-(b2)) * ((b1)-(b2)))
|
2 * ((b1)-(b2)) * ((b1)-(b2)))
|
||||||
|
|
||||||
static Image* ordered_dithering(const Image* src_image,
|
static Image* ordered_dithering(
|
||||||
int offsetx, int offsety,
|
const Image* src_image,
|
||||||
const RgbMap* rgbmap,
|
Image* dst_image,
|
||||||
const Palette* palette)
|
int offsetx, int offsety,
|
||||||
|
const RgbMap* rgbmap,
|
||||||
|
const Palette* palette)
|
||||||
{
|
{
|
||||||
int oppr, oppg, oppb, oppnrcm;
|
int oppr, oppg, oppb, oppnrcm;
|
||||||
Image *dst_image;
|
|
||||||
int dither_const;
|
int dither_const;
|
||||||
int nr, ng, nb;
|
int nr, ng, nb;
|
||||||
int r, g, b, a;
|
int r, g, b, a;
|
||||||
@ -335,10 +345,6 @@ static Image* ordered_dithering(const Image* src_image,
|
|||||||
int x, y;
|
int x, y;
|
||||||
color_t c;
|
color_t c;
|
||||||
|
|
||||||
dst_image = Image::create(IMAGE_INDEXED, src_image->getWidth(), src_image->getHeight());
|
|
||||||
if (!dst_image)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
const LockImageBits<RgbTraits> src_bits(src_image);
|
const LockImageBits<RgbTraits> src_bits(src_image);
|
||||||
LockImageBits<IndexedTraits> dst_bits(dst_image);
|
LockImageBits<IndexedTraits> dst_bits(dst_image);
|
||||||
LockImageBits<RgbTraits>::const_iterator src_it = src_bits.begin();
|
LockImageBits<RgbTraits>::const_iterator src_it = src_bits.begin();
|
||||||
@ -405,7 +411,7 @@ static Image* ordered_dithering(const Image* src_image,
|
|||||||
// Creation of optimized palette for RGB images
|
// Creation of optimized palette for RGB images
|
||||||
// by David Capello
|
// by David Capello
|
||||||
|
|
||||||
static void create_palette_from_bitmaps(const std::vector<Image*>& images, Palette* palette, bool has_background_layer)
|
void create_palette_from_images(const std::vector<Image*>& images, Palette* palette, bool has_background_layer)
|
||||||
{
|
{
|
||||||
quantization::ColorHistogram<5, 6, 5> histogram;
|
quantization::ColorHistogram<5, 6, 5> histogram;
|
||||||
uint32_t color;
|
uint32_t color;
|
||||||
@ -418,16 +424,45 @@ static void create_palette_from_bitmaps(const std::vector<Image*>& images, Palet
|
|||||||
|
|
||||||
for (int i=0; i<(int)images.size(); ++i) {
|
for (int i=0; i<(int)images.size(); ++i) {
|
||||||
const Image* image = images[i];
|
const Image* image = images[i];
|
||||||
const LockImageBits<RgbTraits> bits(image);
|
|
||||||
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
|
||||||
|
|
||||||
for (; it != end; ++it) {
|
switch (image->getPixelFormat()) {
|
||||||
color = *it;
|
|
||||||
|
case IMAGE_RGB:
|
||||||
|
{
|
||||||
|
const LockImageBits<RgbTraits> bits(image);
|
||||||
|
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
||||||
|
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
color = *it;
|
||||||
|
|
||||||
|
if (rgba_geta(color) > 0) {
|
||||||
|
color |= rgba(0, 0, 0, 255);
|
||||||
|
histogram.addSamples(color, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_GRAYSCALE:
|
||||||
|
{
|
||||||
|
const LockImageBits<RgbTraits> bits(image);
|
||||||
|
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
||||||
|
|
||||||
|
for (; it != end; ++it) {
|
||||||
|
color = *it;
|
||||||
|
|
||||||
|
if (graya_geta(color) > 0) {
|
||||||
|
color = graya_getv(color);
|
||||||
|
histogram.addSamples(rgba(color, color, color, 255), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_INDEXED:
|
||||||
|
ASSERT(false);
|
||||||
|
break;
|
||||||
|
|
||||||
if (rgba_geta(color) > 0) {
|
|
||||||
color |= rgba(0, 0, 0, 255);
|
|
||||||
histogram.addSamples(color, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
#include "raster/frame_number.h"
|
#include "raster/frame_number.h"
|
||||||
#include "raster/pixel_format.h"
|
#include "raster/pixel_format.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace raster {
|
namespace raster {
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
@ -34,17 +36,27 @@ namespace raster {
|
|||||||
|
|
||||||
namespace quantization {
|
namespace quantization {
|
||||||
|
|
||||||
|
void create_palette_from_images(
|
||||||
|
const std::vector<Image*>& images,
|
||||||
|
Palette* palette,
|
||||||
|
bool has_background_layer);
|
||||||
|
|
||||||
// Creates a new palette suitable to quantize the given RGB sprite to Indexed color.
|
// Creates a new palette suitable to quantize the given RGB sprite to Indexed color.
|
||||||
Palette* create_palette_from_rgb(const Sprite* sprite, FrameNumber frameNumber);
|
Palette* create_palette_from_rgb(
|
||||||
|
const Sprite* sprite,
|
||||||
|
FrameNumber frameNumber,
|
||||||
|
Palette* newPalette); // Can be NULL to create a new palette
|
||||||
|
|
||||||
// Changes the image pixel format. The dithering method is used only
|
// Changes the image pixel format. The dithering method is used only
|
||||||
// when you want to convert from RGB to Indexed.
|
// when you want to convert from RGB to Indexed.
|
||||||
Image* convert_pixel_format(const Image* image,
|
Image* convert_pixel_format(
|
||||||
PixelFormat pixelFormat,
|
const Image* src,
|
||||||
DitheringMethod ditheringMethod,
|
Image* dst, // Can be NULL to create a new image
|
||||||
const RgbMap* rgbmap,
|
PixelFormat pixelFormat,
|
||||||
const Palette* palette,
|
DitheringMethod ditheringMethod,
|
||||||
bool is_background_layer);
|
const RgbMap* rgbmap,
|
||||||
|
const Palette* palette,
|
||||||
|
bool is_background);
|
||||||
|
|
||||||
} // namespace quantization
|
} // namespace quantization
|
||||||
} // namespace raster
|
} // namespace raster
|
||||||
|
@ -22,85 +22,52 @@
|
|||||||
|
|
||||||
#include "raster/rgbmap.h"
|
#include "raster/rgbmap.h"
|
||||||
|
|
||||||
#include "raster/conversion_alleg.h"
|
#include "raster/color_scales.h"
|
||||||
#include "raster/palette.h"
|
#include "raster/palette.h"
|
||||||
|
|
||||||
#include <allegro.h>
|
|
||||||
|
|
||||||
namespace raster {
|
namespace raster {
|
||||||
|
|
||||||
class RgbMapImpl {
|
#define MAPSIZE 32*32*32
|
||||||
public:
|
|
||||||
RgbMapImpl() {
|
|
||||||
m_allegMap = new RGB_MAP;
|
|
||||||
m_palette = NULL;
|
|
||||||
m_modifications = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
~RgbMapImpl() {
|
|
||||||
delete m_allegMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool match(const Palette* palette) const {
|
|
||||||
return (m_palette == palette &&
|
|
||||||
m_modifications == palette->getModifications());
|
|
||||||
}
|
|
||||||
|
|
||||||
void regenerate(const Palette* palette) {
|
|
||||||
m_palette = palette;
|
|
||||||
m_modifications = palette->getModifications();
|
|
||||||
|
|
||||||
PALETTE allegPal;
|
|
||||||
convert_palette_to_allegro(palette, allegPal);
|
|
||||||
create_rgb_table(m_allegMap, allegPal, NULL);
|
|
||||||
|
|
||||||
for (int r=0; r<32; ++r)
|
|
||||||
for (int g=0; g<32; ++g)
|
|
||||||
for (int b=0; b<32; ++b) {
|
|
||||||
if (m_allegMap->data[r][g][b] >= palette->size())
|
|
||||||
m_allegMap->data[r][g][b] = palette->findBestfit(_rgb_scale_5[r],
|
|
||||||
_rgb_scale_5[g],
|
|
||||||
_rgb_scale_5[b]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int mapColor(int r, int g, int b) const {
|
|
||||||
ASSERT(r >= 0 && r < 256);
|
|
||||||
ASSERT(g >= 0 && g < 256);
|
|
||||||
ASSERT(b >= 0 && b < 256);
|
|
||||||
return m_allegMap->data[r>>3][g>>3][b>>3];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RGB_MAP* m_allegMap;
|
|
||||||
const Palette* m_palette;
|
|
||||||
int m_modifications;
|
|
||||||
};
|
|
||||||
|
|
||||||
RgbMap::RgbMap()
|
RgbMap::RgbMap()
|
||||||
: Object(OBJECT_RGBMAP)
|
: Object(OBJECT_RGBMAP)
|
||||||
|
, m_map(MAPSIZE)
|
||||||
|
, m_palette(NULL)
|
||||||
|
, m_modifications(0)
|
||||||
{
|
{
|
||||||
m_impl = new RgbMapImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
RgbMap::~RgbMap()
|
|
||||||
{
|
|
||||||
delete m_impl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RgbMap::match(const Palette* palette) const
|
bool RgbMap::match(const Palette* palette) const
|
||||||
{
|
{
|
||||||
return m_impl->match(palette);
|
return (m_palette == palette &&
|
||||||
|
m_modifications == palette->getModifications());
|
||||||
}
|
}
|
||||||
|
|
||||||
void RgbMap::regenerate(const Palette* palette)
|
void RgbMap::regenerate(const Palette* palette, int mask_index)
|
||||||
{
|
{
|
||||||
m_impl->regenerate(palette);
|
m_palette = palette;
|
||||||
|
m_modifications = palette->getModifications();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (int r=0; r<32; ++r) {
|
||||||
|
for (int g=0; g<32; ++g) {
|
||||||
|
for (int b=0; b<32; ++b) {
|
||||||
|
m_map[i++] =
|
||||||
|
palette->findBestfit(
|
||||||
|
scale_5bits_to_8bits(r),
|
||||||
|
scale_5bits_to_8bits(g),
|
||||||
|
scale_5bits_to_8bits(b), mask_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int RgbMap::mapColor(int r, int g, int b) const
|
int RgbMap::mapColor(int r, int g, int b) const
|
||||||
{
|
{
|
||||||
return m_impl->mapColor(r, g, b);
|
ASSERT(r >= 0 && r < 256);
|
||||||
|
ASSERT(g >= 0 && g < 256);
|
||||||
|
ASSERT(b >= 0 && b < 256);
|
||||||
|
return m_map[((r>>3) << 10) + ((g>>3) << 5) + (b>>3)];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace raster
|
} // namespace raster
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
#include "base/disable_copying.h"
|
#include "base/disable_copying.h"
|
||||||
#include "raster/object.h"
|
#include "raster/object.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace raster {
|
namespace raster {
|
||||||
|
|
||||||
class Palette;
|
class Palette;
|
||||||
@ -30,15 +32,16 @@ namespace raster {
|
|||||||
class RgbMap : public Object {
|
class RgbMap : public Object {
|
||||||
public:
|
public:
|
||||||
RgbMap();
|
RgbMap();
|
||||||
virtual ~RgbMap();
|
|
||||||
|
|
||||||
bool match(const Palette* palette) const;
|
bool match(const Palette* palette) const;
|
||||||
void regenerate(const Palette* palette);
|
void regenerate(const Palette* palette, int mask_index);
|
||||||
|
|
||||||
int mapColor(int r, int g, int b) const;
|
int mapColor(int r, int g, int b) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class RgbMapImpl* m_impl;
|
std::vector<uint8_t> m_map;
|
||||||
|
const Palette* m_palette;
|
||||||
|
int m_modifications;
|
||||||
|
|
||||||
DISABLE_COPYING(RgbMap);
|
DISABLE_COPYING(RgbMap);
|
||||||
};
|
};
|
||||||
|
@ -278,13 +278,16 @@ void Sprite::deletePalette(Palette* pal)
|
|||||||
|
|
||||||
RgbMap* Sprite::getRgbMap(FrameNumber frame)
|
RgbMap* Sprite::getRgbMap(FrameNumber frame)
|
||||||
{
|
{
|
||||||
|
int mask_color = (getBackgroundLayer() ? -1: getTransparentColor());
|
||||||
|
|
||||||
if (m_rgbMap == NULL) {
|
if (m_rgbMap == NULL) {
|
||||||
m_rgbMap = new RgbMap();
|
m_rgbMap = new RgbMap();
|
||||||
m_rgbMap->regenerate(getPalette(frame));
|
m_rgbMap->regenerate(getPalette(frame), mask_color);
|
||||||
}
|
}
|
||||||
else if (!m_rgbMap->match(getPalette(frame))) {
|
else if (!m_rgbMap->match(getPalette(frame))) {
|
||||||
m_rgbMap->regenerate(getPalette(frame));
|
m_rgbMap->regenerate(getPalette(frame), mask_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_rgbMap;
|
return m_rgbMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user