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:
David Capello 2014-07-19 22:01:39 -03:00
parent b9a1fa8e17
commit 411ceda0e7
26 changed files with 788 additions and 222 deletions

View 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="&amp;One palette for each frame" group="1" />
<radio id="quantize_all" text="&amp;One palette for all frames" group="1" />
<radio id="no_quantize" text="&amp;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="&amp;OK" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="&amp;Cancel" closewindow="true" />
</hbox>
</hbox>
</vbox>
</window>
</gui>

View File

@ -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());

View File

@ -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) {

View File

@ -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");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

@ -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());

View File

@ -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(&current_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, &current_palette, has_background);
}
break;
}
rgbmap.regenerate(&current_palette, transparent_index);
quantization::convert_pixel_format(
buffer_image,
current_image,
IMAGE_INDEXED,
gif_options->dithering(),
&rgbmap,
&current_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

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

View File

@ -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");

View File

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

View File

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

View File

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

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

View File

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