mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-17 08:43:11 +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();
|
||||
it != end; ++it) {
|
||||
// Load the sprite
|
||||
Document* document = load_document(it->c_str());
|
||||
Document* document = load_document(context, it->c_str());
|
||||
if (!document) {
|
||||
if (!isGui())
|
||||
console.printf("Error loading file \"%s\"\n", it->c_str());
|
||||
|
@ -126,7 +126,7 @@ void OpenFileCommand::onExecute(Context* context)
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (fop) {
|
||||
|
@ -587,7 +587,8 @@ void PaletteEntryEditor::onQuantizeClick(Event& ev)
|
||||
return;
|
||||
}
|
||||
|
||||
palette = quantization::create_palette_from_rgb(sprite, reader.frame());
|
||||
palette = quantization::create_palette_from_rgb(
|
||||
sprite, reader.frame(), NULL);
|
||||
}
|
||||
|
||||
setNewPalette(palette, "Quantize Palette");
|
||||
|
@ -81,9 +81,9 @@ private:
|
||||
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)
|
||||
return;
|
||||
|
||||
@ -192,7 +192,7 @@ protected:
|
||||
m_selectedFilename = filename;
|
||||
|
||||
// Save the document
|
||||
save_document_in_background(documentWriter, markAsSaved);
|
||||
save_document_in_background(writer.context(), documentWriter, markAsSaved);
|
||||
|
||||
if (documentWriter->isModified())
|
||||
documentWriter->setFilename(oldFilename);
|
||||
@ -251,7 +251,7 @@ void SaveFileCommand::onExecute(Context* context)
|
||||
if (!confirmReadonly(documentWriter->getFilename()))
|
||||
return;
|
||||
|
||||
save_document_in_background(documentWriter, true);
|
||||
save_document_in_background(context, documentWriter, true);
|
||||
update_screen_for_document(documentWriter);
|
||||
}
|
||||
// If the document isn't associated to a file, we must to show the
|
||||
|
@ -32,6 +32,11 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
Context::Context()
|
||||
: m_settings(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Context::Context(ISettings* settings)
|
||||
: m_settings(settings)
|
||||
{
|
||||
@ -159,4 +164,9 @@ void Context::onRemoveDocument(Document* document)
|
||||
m_observers.notifyRemoveDocument(this, document);
|
||||
}
|
||||
|
||||
void Context::onGetActiveLocation(DocumentLocation* location) const
|
||||
{
|
||||
// Without active location
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -44,6 +44,9 @@ namespace app {
|
||||
|
||||
class Context {
|
||||
public:
|
||||
Context();
|
||||
// The "settings" are deleted automatically in the ~Context destructor
|
||||
Context(ISettings* settings);
|
||||
virtual ~Context();
|
||||
|
||||
virtual bool isUiAvailable() const { return false; }
|
||||
@ -76,19 +79,11 @@ namespace app {
|
||||
void removeObserver(ContextObserver* observer);
|
||||
|
||||
protected:
|
||||
|
||||
// The "settings" are deleted automatically in the ~Context destructor
|
||||
Context(ISettings* settings);
|
||||
|
||||
virtual void onAddDocument(Document* document);
|
||||
virtual void onRemoveDocument(Document* document);
|
||||
virtual void onGetActiveLocation(DocumentLocation* location) const = 0;
|
||||
virtual void onGetActiveLocation(DocumentLocation* location) const;
|
||||
|
||||
private:
|
||||
|
||||
// Without default constructor
|
||||
Context();
|
||||
|
||||
// List of all documents.
|
||||
Documents m_documents;
|
||||
|
||||
|
@ -140,6 +140,7 @@ namespace app {
|
||||
// Loaded options from file
|
||||
|
||||
void setFormatOptions(const SharedPtr<FormatOptions>& format_options);
|
||||
SharedPtr<FormatOptions> getFormatOptions() { return m_format_options; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Boundaries
|
||||
|
@ -187,7 +187,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin
|
||||
}
|
||||
|
||||
new_image = quantization::convert_pixel_format
|
||||
(old_image, newFormat, dithering_method, rgbmap,
|
||||
(old_image, NULL, newFormat, dithering_method, rgbmap,
|
||||
sprite->getPalette(frame),
|
||||
is_image_from_background);
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "app/document.h"
|
||||
#include "app/document_api.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/path.h"
|
||||
#include "base/unique_ptr.h"
|
||||
@ -178,7 +179,7 @@ void DocumentExporter::exportSheet()
|
||||
// Save the image files.
|
||||
if (!m_textureFilename.empty()) {
|
||||
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/app.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/document.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/file_formats_manager.h"
|
||||
@ -48,7 +48,7 @@ namespace app {
|
||||
|
||||
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 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;
|
||||
|
||||
/* 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)
|
||||
return NULL;
|
||||
|
||||
@ -116,10 +116,10 @@ Document* load_document(const char* filename)
|
||||
return document;
|
||||
}
|
||||
|
||||
int save_document(Document* document)
|
||||
int save_document(Context* context, Document* document)
|
||||
{
|
||||
int ret;
|
||||
FileOp* fop = fop_to_save_document(document);
|
||||
FileOp* fop = fop_to_save_document(context, document);
|
||||
if (!fop)
|
||||
return -1;
|
||||
|
||||
@ -138,11 +138,11 @@ int save_document(Document* document)
|
||||
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;
|
||||
|
||||
fop = fop_new(FileOpLoad);
|
||||
fop = fop_new(FileOpLoad, context);
|
||||
if (!fop)
|
||||
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 */
|
||||
if ((flags & FILE_LOAD_SEQUENCE_ASK) &&
|
||||
App::instance()->isGui()) {
|
||||
if ((flags & FILE_LOAD_SEQUENCE_ASK) && context->isUiAvailable()) {
|
||||
/* really want load all files? */
|
||||
if ((fop->seq.filename_list.size() > 1) &&
|
||||
(ui::Alert::show("Notice"
|
||||
@ -231,12 +230,12 @@ done:;
|
||||
return fop;
|
||||
}
|
||||
|
||||
FileOp* fop_to_save_document(Document* document)
|
||||
FileOp* fop_to_save_document(Context* context, Document* document)
|
||||
{
|
||||
FileOp *fop;
|
||||
bool fatal;
|
||||
|
||||
fop = fop_new(FileOpSave);
|
||||
fop = fop_new(FileOpSave, context);
|
||||
if (!fop)
|
||||
return NULL;
|
||||
|
||||
@ -323,7 +322,7 @@ FileOp* fop_to_save_document(Document* document)
|
||||
// Show the confirmation alert
|
||||
if (!warnings.empty()) {
|
||||
// Interative
|
||||
if (App::instance()->isGui()) {
|
||||
if (context->isUiAvailable()) {
|
||||
int ret;
|
||||
|
||||
if (fatal)
|
||||
@ -663,8 +662,9 @@ void fop_post_load(FileOp* fop)
|
||||
fop->document->getSprite()->getPalettes().size() <= 1 &&
|
||||
fop->document->getSprite()->getPalette(FrameNumber(0))->isBlack()) {
|
||||
SharedPtr<Palette> palette
|
||||
(quantization::create_palette_from_rgb(fop->document->getSprite(),
|
||||
FrameNumber(0)));
|
||||
(quantization::create_palette_from_rgb(
|
||||
fop->document->getSprite(),
|
||||
FrameNumber(0), NULL));
|
||||
|
||||
fop->document->getSprite()->resetPalettes();
|
||||
fop->document->getSprite()->setPalette(palette, false);
|
||||
@ -802,13 +802,14 @@ bool fop_is_stop(FileOp *fop)
|
||||
return stop;
|
||||
}
|
||||
|
||||
static FileOp* fop_new(FileOpType type)
|
||||
static FileOp* fop_new(FileOpType type, Context* context)
|
||||
{
|
||||
FileOp* fop = new FileOp;
|
||||
|
||||
fop->type = type;
|
||||
fop->format = NULL;
|
||||
fop->format_data = NULL;
|
||||
fop->context = context;
|
||||
fop->document = NULL;
|
||||
|
||||
fop->mutex = new base::mutex();
|
||||
|
@ -46,6 +46,7 @@ namespace raster {
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Context;
|
||||
class Document;
|
||||
class FileFormat;
|
||||
class FormatOptions;
|
||||
@ -70,6 +71,7 @@ namespace app {
|
||||
FileOpType type; // Operation type: 0=load, 1=save.
|
||||
FileFormat* format;
|
||||
void* format_data; // Custom data for the FileFormat::onLoad/onSave operations.
|
||||
Context* context;
|
||||
Document* document; // Loaded document, or document to be saved.
|
||||
std::string filename; // File-name to load/save.
|
||||
|
||||
@ -119,13 +121,13 @@ namespace app {
|
||||
|
||||
// High-level routines to load/save documents.
|
||||
|
||||
Document* load_document(const char* filename);
|
||||
int save_document(Document* document);
|
||||
Document* load_document(Context* context, const char* filename);
|
||||
int save_document(Context* context, Document* document);
|
||||
|
||||
// Low-level routines to load/save documents.
|
||||
|
||||
FileOp* fop_to_load_document(const char* filename, int flags);
|
||||
FileOp* fop_to_save_document(Document* document);
|
||||
FileOp* fop_to_load_document(Context* context, const char* filename, int flags);
|
||||
FileOp* fop_to_save_document(Context* context, Document* document);
|
||||
void fop_operate(FileOp* fop, IFileOpProgress* progress);
|
||||
void fop_done(FileOp* fop);
|
||||
void fop_stop(FileOp* fop);
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "tests/test.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/context.h"
|
||||
#include "app/document.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_formats_manager.h"
|
||||
@ -37,6 +38,7 @@ TEST(File, SeveralSizes)
|
||||
// Register all possible image formats.
|
||||
FileFormatsManager::instance().registerAllFormats();
|
||||
std::vector<char> fn(256);
|
||||
app::Context context;
|
||||
|
||||
for (int w=10; w<=10+503*2; w+=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(h, doc->getSprite()->getHeight());
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -20,16 +20,23 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/document.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.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/util/autocrop.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/unique_ptr.h"
|
||||
#include "raster/raster.h"
|
||||
#include "ui/alert.h"
|
||||
#include "ui/button.h"
|
||||
|
||||
#include "generated_gif_options.h"
|
||||
|
||||
#include <gif_lib.h>
|
||||
|
||||
@ -72,6 +79,7 @@ struct GifData
|
||||
};
|
||||
|
||||
class GifFormat : public FileFormat {
|
||||
|
||||
const char* onGetName() const { return "gif"; }
|
||||
const char* onGetExtensions() const { return "gif"; }
|
||||
int onGetFlags() const {
|
||||
@ -84,7 +92,8 @@ class GifFormat : public FileFormat {
|
||||
FILE_SUPPORT_GRAYA |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_FRAMES |
|
||||
FILE_SUPPORT_PALETTES;
|
||||
FILE_SUPPORT_PALETTES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop);
|
||||
@ -93,6 +102,7 @@ class GifFormat : public FileFormat {
|
||||
#ifdef ENABLE_SAVE
|
||||
bool onSave(FileOp* fop) OVERRIDE;
|
||||
#endif
|
||||
SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) OVERRIDE;
|
||||
};
|
||||
|
||||
FileFormat* CreateGifFormat()
|
||||
@ -157,7 +167,8 @@ bool GifFormat::onLoad(FileOp* fop)
|
||||
ColorMapObject* colormap = gif_file->SColorMap;
|
||||
for (int i=0; i<colormap->ColorCount; ++i) {
|
||||
current_palette->setEntry(i,
|
||||
rgba(colormap->Colors[i].Red,
|
||||
rgba(
|
||||
colormap->Colors[i].Red,
|
||||
colormap->Colors[i].Green,
|
||||
colormap->Colors[i].Blue, 255));
|
||||
}
|
||||
@ -208,9 +219,11 @@ bool GifFormat::onLoad(FileOp* fop)
|
||||
if (gif_file->Image.ColorMap) {
|
||||
ColorMapObject* colormap = gif_file->Image.ColorMap;
|
||||
for (int i=0; i<colormap->ColorCount; ++i) {
|
||||
current_palette->setEntry(i, rgba(colormap->Colors[i].Red,
|
||||
colormap->Colors[i].Green,
|
||||
colormap->Colors[i].Blue, 255));
|
||||
current_palette->setEntry(i,
|
||||
rgba(
|
||||
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].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;
|
||||
|
||||
@ -272,8 +285,8 @@ bool GifFormat::onLoad(FileOp* fop)
|
||||
transparent_index = (extension[1] & 1) ? extension[4]: -1;
|
||||
frame_delay = (extension[3] << 8) | extension[2];
|
||||
|
||||
TRACE("Disposal method: %d\nTransparent index: %d\nFrame delay: %d\n",
|
||||
disposal_method, transparent_index, frame_delay);
|
||||
// PRINTF("Disposal method: %d\nTransparent index: %d\nFrame delay: %d\n",
|
||||
// disposal_method, transparent_index, frame_delay);
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,7 +368,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
|
||||
ui::Alert::show("GIF Conversion"
|
||||
"<<The selected file: %s"
|
||||
"<<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?"
|
||||
"||Convert to &RGBA||Keep &Indexed||&Cancel",
|
||||
fop->document->getFilename().c_str());
|
||||
@ -367,6 +380,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
// Create the main layer
|
||||
@ -524,26 +538,58 @@ bool GifFormat::onSave(FileOp* fop)
|
||||
if (!gif_file)
|
||||
throw Exception("Error creating GIF file.\n");
|
||||
|
||||
SharedPtr<GifOptions> gif_options = fop->seq.format_options;
|
||||
Sprite* sprite = fop->document->getSprite();
|
||||
int sprite_w = sprite->getWidth();
|
||||
int sprite_h = sprite->getHeight();
|
||||
PixelFormat sprite_format = sprite->getPixelFormat();
|
||||
bool interlace = false;
|
||||
bool interlaced = gif_options->interlaced();
|
||||
int loop = 0;
|
||||
bool has_background = (sprite->getBackgroundLayer() ? true: false);
|
||||
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* previous_palette = current_palette;
|
||||
ColorMapObject* color_map = GifMakeMapObject(current_palette->size(), NULL);
|
||||
for (int 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));
|
||||
Palette current_palette = *sprite->getPalette(FrameNumber(0));
|
||||
Palette previous_palette(current_palette);
|
||||
RgbMap rgbmap;
|
||||
|
||||
// The color map must be a power of two.
|
||||
int color_map_size = current_palette.size();
|
||||
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,
|
||||
color_map->BitsPerPixel,
|
||||
ColorMapObject* color_map = NULL;
|
||||
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)
|
||||
throw Exception("Error writing GIF header.\n");
|
||||
|
||||
@ -562,44 +608,40 @@ bool GifFormat::onSave(FileOp* fop)
|
||||
clear_image(current_image, background_color);
|
||||
clear_image(previous_image, background_color);
|
||||
|
||||
for (FrameNumber frame_num(0); frame_num<sprite->getTotalFrames(); ++frame_num) {
|
||||
current_palette = sprite->getPalette(frame_num);
|
||||
ColorMapObject* image_color_map = NULL;
|
||||
|
||||
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 (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);
|
||||
|
||||
switch (sprite_format) {
|
||||
|
||||
// Convert the RGB image to Indexed
|
||||
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);
|
||||
}
|
||||
switch (gif_options->quantize()) {
|
||||
case GifOptions::NoQuantize:
|
||||
sprite->getPalette(frame_num)->copyColorsTo(¤t_palette);
|
||||
break;
|
||||
case GifOptions::QuantizeEach:
|
||||
case GifOptions::QuantizeAll:
|
||||
{
|
||||
current_palette.makeBlack();
|
||||
|
||||
// Convert the Grayscale image to Indexed
|
||||
case IMAGE_GRAYSCALE:
|
||||
for (int y = 0; y < sprite_h; ++y)
|
||||
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);
|
||||
}
|
||||
std::vector<Image*> imgarray(1);
|
||||
imgarray[0] = buffer_image;
|
||||
raster::quantization::create_palette_from_images(imgarray, ¤t_palette, has_background);
|
||||
}
|
||||
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".
|
||||
else {
|
||||
@ -679,26 +721,32 @@ bool GifFormat::onSave(FileOp* fop)
|
||||
}
|
||||
|
||||
// Image color map
|
||||
ColorMapObject* image_color_map = NULL;
|
||||
if (current_palette != previous_palette) {
|
||||
image_color_map = GifMakeMapObject(current_palette->size(), NULL);
|
||||
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));
|
||||
if ((!color_map && frame_num == 0) ||
|
||||
(current_palette.countDiff(&previous_palette, NULL, NULL) > 0)) {
|
||||
if (!image_color_map) {
|
||||
image_color_map = GifMakeMapObject(current_palette.size(), NULL);
|
||||
if (image_color_map == NULL)
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
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.
|
||||
if (EGifPutImageDesc(gif_file,
|
||||
frame_x, frame_y,
|
||||
frame_w, frame_h, interlace ? 1: 0,
|
||||
frame_w, frame_h, interlaced ? 1: 0,
|
||||
image_color_map) == GIF_ERROR)
|
||||
throw Exception("Error writing GIF frame %d.\n", (int)frame_num);
|
||||
|
||||
// Write the image data (pixels).
|
||||
if (interlace) {
|
||||
if (interlaced) {
|
||||
// Need to perform 4 passes on the images.
|
||||
for (int i=0; i<4; ++i)
|
||||
for (int y = interlaced_offset[i]; y < frame_h; y += interlaced_jumps[i]) {
|
||||
@ -711,7 +759,7 @@ bool GifFormat::onSave(FileOp* fop)
|
||||
}
|
||||
else {
|
||||
// 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)current_image->getPixelAddress(frame_x, frame_y + y);
|
||||
|
||||
@ -727,4 +775,69 @@ bool GifFormat::onSave(FileOp* fop)
|
||||
}
|
||||
#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
|
||||
|
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/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/document.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/format_options.h"
|
||||
@ -358,15 +360,21 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||
// Shows the JPEG configuration dialog.
|
||||
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 {
|
||||
// Configuration parameters
|
||||
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.
|
||||
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");
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "app/thumbnail_generator.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/context.h"
|
||||
#include "app/document.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file_system.h"
|
||||
@ -190,9 +191,12 @@ void ThumbnailGenerator::addWorkerToGenerateThumbnail(IFileItem* fileitem)
|
||||
getWorkerStatus(fileitem, progress) != WithoutWorker)
|
||||
return;
|
||||
|
||||
FileOp* fop = fop_to_load_document(fileitem->getFileName().c_str(),
|
||||
FILE_LOAD_SEQUENCE_NONE |
|
||||
FILE_LOAD_ONE_FRAME);
|
||||
Context tmpContext;
|
||||
FileOp* fop = fop_to_load_document(&tmpContext,
|
||||
fileitem->getFileName().c_str(),
|
||||
FILE_LOAD_SEQUENCE_NONE |
|
||||
FILE_LOAD_ONE_FRAME);
|
||||
|
||||
if (!fop)
|
||||
return;
|
||||
|
||||
|
@ -204,7 +204,7 @@ void clipboard::paste()
|
||||
RgbMap* dst_rgbmap = dst_sprite->getRgbMap(editor->getFrame());
|
||||
|
||||
src_image = quantization::convert_pixel_format(
|
||||
clipboard_image, dst_sprite->getPixelFormat(),
|
||||
clipboard_image, NULL, dst_sprite->getPixelFormat(),
|
||||
DITHERING_NONE, dst_rgbmap, clipboard_palette,
|
||||
false);
|
||||
}
|
||||
|
@ -63,6 +63,10 @@ namespace raster {
|
||||
(a << rgba_a_shift));
|
||||
}
|
||||
|
||||
inline uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return rgba(0, 0, 0, 255);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Grayscale
|
||||
|
||||
@ -85,6 +89,10 @@ namespace raster {
|
||||
(a << graya_a_shift));
|
||||
}
|
||||
|
||||
inline uint16_t gray(uint8_t v) {
|
||||
return graya(v, 255);
|
||||
}
|
||||
|
||||
} // namespace raster
|
||||
|
||||
#endif
|
||||
|
@ -46,9 +46,9 @@ Palette::Palette(FrameNumber frame, int ncolors)
|
||||
|
||||
m_frame = frame;
|
||||
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)
|
||||
|
@ -45,17 +45,22 @@ namespace quantization {
|
||||
using namespace gfx;
|
||||
|
||||
// Converts a RGB image to indexed with ordered dithering method.
|
||||
static Image* ordered_dithering(const Image* src_image,
|
||||
int offsetx, int offsety,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette);
|
||||
static Image* ordered_dithering(
|
||||
const Image* src_image,
|
||||
Image* dst_image,
|
||||
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, FrameNumber frameNumber)
|
||||
Palette* create_palette_from_rgb(
|
||||
const Sprite* sprite,
|
||||
FrameNumber frameNumber,
|
||||
Palette* palette)
|
||||
{
|
||||
if (!palette)
|
||||
palette = new Palette(FrameNumber(0), 256);
|
||||
|
||||
bool has_background_layer = (sprite->getBackgroundLayer() != NULL);
|
||||
Palette* palette = new Palette(FrameNumber(0), 256);
|
||||
Image* flat_image;
|
||||
|
||||
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'
|
||||
|
||||
// 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;
|
||||
return palette;
|
||||
}
|
||||
|
||||
Image* convert_pixel_format(const Image* image,
|
||||
PixelFormat pixelFormat,
|
||||
DitheringMethod ditheringMethod,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette,
|
||||
bool is_background_layer)
|
||||
Image* convert_pixel_format(
|
||||
const Image* image,
|
||||
Image* new_image,
|
||||
PixelFormat pixelFormat,
|
||||
DitheringMethod ditheringMethod,
|
||||
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
|
||||
if (image->getPixelFormat() == IMAGE_RGB &&
|
||||
pixelFormat == IMAGE_INDEXED &&
|
||||
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;
|
||||
int r, g, b;
|
||||
|
||||
@ -223,7 +232,7 @@ Image* convert_pixel_format(const Image* image,
|
||||
ASSERT(dst_it != dst_end);
|
||||
c = *src_it;
|
||||
|
||||
if (!is_background_layer && c == image->getMaskColor())
|
||||
if (!is_background && c == image->getMaskColor())
|
||||
*dst_it = 0;
|
||||
else
|
||||
*dst_it = rgba(rgba_getr(palette->getEntry(c)),
|
||||
@ -243,7 +252,7 @@ Image* convert_pixel_format(const Image* image,
|
||||
ASSERT(dst_it != dst_end);
|
||||
c = *src_it;
|
||||
|
||||
if (!is_background_layer && c == image->getMaskColor())
|
||||
if (!is_background && c == image->getMaskColor())
|
||||
*dst_it = 0;
|
||||
else {
|
||||
r = rgba_getr(palette->getEntry(c));
|
||||
@ -268,7 +277,7 @@ Image* convert_pixel_format(const Image* image,
|
||||
ASSERT(dst_it != dst_end);
|
||||
c = *src_it;
|
||||
|
||||
if (!is_background_layer && c == image->getMaskColor())
|
||||
if (!is_background && c == image->getMaskColor())
|
||||
*dst_it = dstMaskColor;
|
||||
else {
|
||||
r = rgba_getr(palette->getEntry(c));
|
||||
@ -321,13 +330,14 @@ static int pattern[8][8] = {
|
||||
4 * ((g1)-(g2)) * ((g1)-(g2)) + \
|
||||
2 * ((b1)-(b2)) * ((b1)-(b2)))
|
||||
|
||||
static Image* ordered_dithering(const Image* src_image,
|
||||
int offsetx, int offsety,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette)
|
||||
static Image* ordered_dithering(
|
||||
const Image* src_image,
|
||||
Image* dst_image,
|
||||
int offsetx, int offsety,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette)
|
||||
{
|
||||
int oppr, oppg, oppb, oppnrcm;
|
||||
Image *dst_image;
|
||||
int dither_const;
|
||||
int nr, ng, nb;
|
||||
int r, g, b, a;
|
||||
@ -335,10 +345,6 @@ static Image* ordered_dithering(const Image* src_image,
|
||||
int x, y;
|
||||
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);
|
||||
LockImageBits<IndexedTraits> dst_bits(dst_image);
|
||||
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
|
||||
// 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;
|
||||
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) {
|
||||
const Image* image = images[i];
|
||||
const LockImageBits<RgbTraits> bits(image);
|
||||
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
||||
|
||||
for (; it != end; ++it) {
|
||||
color = *it;
|
||||
switch (image->getPixelFormat()) {
|
||||
|
||||
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/pixel_format.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace raster {
|
||||
|
||||
class Image;
|
||||
@ -34,17 +36,27 @@ namespace raster {
|
||||
|
||||
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.
|
||||
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
|
||||
// when you want to convert from RGB to Indexed.
|
||||
Image* convert_pixel_format(const Image* image,
|
||||
PixelFormat pixelFormat,
|
||||
DitheringMethod ditheringMethod,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette,
|
||||
bool is_background_layer);
|
||||
Image* convert_pixel_format(
|
||||
const Image* src,
|
||||
Image* dst, // Can be NULL to create a new image
|
||||
PixelFormat pixelFormat,
|
||||
DitheringMethod ditheringMethod,
|
||||
const RgbMap* rgbmap,
|
||||
const Palette* palette,
|
||||
bool is_background);
|
||||
|
||||
} // namespace quantization
|
||||
} // namespace raster
|
||||
|
@ -22,85 +22,52 @@
|
||||
|
||||
#include "raster/rgbmap.h"
|
||||
|
||||
#include "raster/conversion_alleg.h"
|
||||
#include "raster/color_scales.h"
|
||||
#include "raster/palette.h"
|
||||
|
||||
#include <allegro.h>
|
||||
|
||||
namespace raster {
|
||||
|
||||
class RgbMapImpl {
|
||||
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;
|
||||
};
|
||||
#define MAPSIZE 32*32*32
|
||||
|
||||
RgbMap::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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "base/disable_copying.h"
|
||||
#include "raster/object.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace raster {
|
||||
|
||||
class Palette;
|
||||
@ -30,15 +32,16 @@ namespace raster {
|
||||
class RgbMap : public Object {
|
||||
public:
|
||||
RgbMap();
|
||||
virtual ~RgbMap();
|
||||
|
||||
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;
|
||||
|
||||
private:
|
||||
class RgbMapImpl* m_impl;
|
||||
std::vector<uint8_t> m_map;
|
||||
const Palette* m_palette;
|
||||
int m_modifications;
|
||||
|
||||
DISABLE_COPYING(RgbMap);
|
||||
};
|
||||
|
@ -278,13 +278,16 @@ void Sprite::deletePalette(Palette* pal)
|
||||
|
||||
RgbMap* Sprite::getRgbMap(FrameNumber frame)
|
||||
{
|
||||
int mask_color = (getBackgroundLayer() ? -1: getTransparentColor());
|
||||
|
||||
if (m_rgbMap == NULL) {
|
||||
m_rgbMap = new RgbMap();
|
||||
m_rgbMap->regenerate(getPalette(frame));
|
||||
m_rgbMap->regenerate(getPalette(frame), mask_color);
|
||||
}
|
||||
else if (!m_rgbMap->match(getPalette(frame))) {
|
||||
m_rgbMap->regenerate(getPalette(frame));
|
||||
m_rgbMap->regenerate(getPalette(frame), mask_color);
|
||||
}
|
||||
|
||||
return m_rgbMap;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user