mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-04 05:50:15 +00:00
Fix issue #9: GIF files are loaded as Indexed images now.
+ Added support for non-zero transparent index. + Added fop_post_load() to do post-load processing which need user-interaction. + Added FileFormat::onPostLoad/onDestroyData members. + Added Document::addSprite().
This commit is contained in:
parent
ff481003c8
commit
c32551db64
@ -180,6 +180,9 @@ void OpenFileCommand::onExecute(Context* context)
|
|||||||
fop_stop(data->fop);
|
fop_stop(data->fop);
|
||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
|
// Post-load processing, it is called from the GUI because may require user intervention.
|
||||||
|
fop_post_load(fop);
|
||||||
|
|
||||||
// Show any error
|
// Show any error
|
||||||
if (fop->has_error())
|
if (fop->has_error())
|
||||||
console.printf(fop->error.c_str());
|
console.printf(fop->error.c_str());
|
||||||
|
@ -126,6 +126,12 @@ Document* Document::createBasicDocument(int imgtype, int width, int height, int
|
|||||||
return document.release(); // Release the document (it does not throw) returning the raw pointer
|
return document.release(); // Release the document (it does not throw) returning the raw pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Document::addSprite(Sprite* sprite)
|
||||||
|
{
|
||||||
|
ASSERT(m_sprite == NULL); // TODO add support for more sprites in the future (e.g. for .ico files)
|
||||||
|
m_sprite.reset(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
const char* Document::getFilename() const
|
const char* Document::getFilename() const
|
||||||
{
|
{
|
||||||
return m_filename.c_str();
|
return m_filename.c_str();
|
||||||
|
@ -84,6 +84,8 @@ public:
|
|||||||
Sprite* getSprite() { return m_sprite; }
|
Sprite* getSprite() { return m_sprite; }
|
||||||
undo::UndoHistory* getUndoHistory() { return m_undoHistory; }
|
undo::UndoHistory* getUndoHistory() { return m_undoHistory; }
|
||||||
|
|
||||||
|
void addSprite(Sprite* sprite);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// File related properties
|
// File related properties
|
||||||
|
|
||||||
|
@ -94,6 +94,8 @@ Document* load_document(const char* filename)
|
|||||||
fop_operate(fop);
|
fop_operate(fop);
|
||||||
fop_done(fop);
|
fop_done(fop);
|
||||||
|
|
||||||
|
fop_post_load(fop);
|
||||||
|
|
||||||
if (fop->has_error()) {
|
if (fop->has_error()) {
|
||||||
Console console;
|
Console console;
|
||||||
console.printf(fop->error.c_str());
|
console.printf(fop->error.c_str());
|
||||||
@ -528,32 +530,6 @@ void fop_operate(FileOp *fop)
|
|||||||
fop_error(fop, "Error loading sprite from file \"%s\"\n",
|
fop_error(fop, "Error loading sprite from file \"%s\"\n",
|
||||||
fop->filename.c_str());
|
fop->filename.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fop->document != NULL && fop->document->getSprite() != NULL) {
|
|
||||||
// Select the last layer
|
|
||||||
if (fop->document->getSprite()->getFolder()->get_layers_count() > 0) {
|
|
||||||
LayerIterator last_layer = --fop->document->getSprite()->getFolder()->get_layer_end();
|
|
||||||
fop->document->getSprite()->setCurrentLayer(*last_layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the filename.
|
|
||||||
if (fop->is_sequence())
|
|
||||||
fop->document->setFilename(fop->seq.filename_list.begin()->c_str());
|
|
||||||
else
|
|
||||||
fop->document->setFilename(fop->filename.c_str());
|
|
||||||
|
|
||||||
// Creates a suitable palette for RGB images
|
|
||||||
if (fop->document->getSprite()->getImgType() == IMAGE_RGB &&
|
|
||||||
fop->document->getSprite()->getPalettes().size() <= 1 &&
|
|
||||||
fop->document->getSprite()->getPalette(0)->isBlack()) {
|
|
||||||
SharedPtr<Palette> palette(quantization::create_palette_from_rgb(fop->document->getSprite()));
|
|
||||||
|
|
||||||
fop->document->getSprite()->resetPalettes();
|
|
||||||
fop->document->getSprite()->setPalette(palette, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
fop->document->markAsSaved();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Save //////////////////////////////////////////////////////////////////////
|
// Save //////////////////////////////////////////////////////////////////////
|
||||||
else if (fop->type == FileOpSave &&
|
else if (fop->type == FileOpSave &&
|
||||||
@ -640,6 +616,8 @@ void fop_stop(FileOp *fop)
|
|||||||
|
|
||||||
void fop_free(FileOp *fop)
|
void fop_free(FileOp *fop)
|
||||||
{
|
{
|
||||||
|
fop->format->destroyData(fop);
|
||||||
|
|
||||||
if (fop->seq.palette != NULL)
|
if (fop->seq.palette != NULL)
|
||||||
delete fop->seq.palette;
|
delete fop->seq.palette;
|
||||||
|
|
||||||
@ -649,6 +627,47 @@ void fop_free(FileOp *fop)
|
|||||||
delete fop;
|
delete fop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fop_post_load(FileOp* fop)
|
||||||
|
{
|
||||||
|
if (fop->document == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Set the filename.
|
||||||
|
if (fop->is_sequence())
|
||||||
|
fop->document->setFilename(fop->seq.filename_list.begin()->c_str());
|
||||||
|
else
|
||||||
|
fop->document->setFilename(fop->filename.c_str());
|
||||||
|
|
||||||
|
bool result = fop->format->postLoad(fop);
|
||||||
|
if (!result) {
|
||||||
|
// Destroy the document
|
||||||
|
delete fop->document;
|
||||||
|
fop->document = NULL;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fop->document->getSprite() != NULL) {
|
||||||
|
// Select the last layer
|
||||||
|
if (fop->document->getSprite()->getFolder()->get_layers_count() > 0) {
|
||||||
|
LayerIterator last_layer = --fop->document->getSprite()->getFolder()->get_layer_end();
|
||||||
|
fop->document->getSprite()->setCurrentLayer(*last_layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a suitable palette for RGB images
|
||||||
|
if (fop->document->getSprite()->getImgType() == IMAGE_RGB &&
|
||||||
|
fop->document->getSprite()->getPalettes().size() <= 1 &&
|
||||||
|
fop->document->getSprite()->getPalette(0)->isBlack()) {
|
||||||
|
SharedPtr<Palette> palette(quantization::create_palette_from_rgb(fop->document->getSprite()));
|
||||||
|
|
||||||
|
fop->document->getSprite()->resetPalettes();
|
||||||
|
fop->document->getSprite()->setPalette(palette, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fop->document->markAsSaved();
|
||||||
|
}
|
||||||
|
|
||||||
void fop_sequence_set_format_options(FileOp* fop, const SharedPtr<FormatOptions>& format_options)
|
void fop_sequence_set_format_options(FileOp* fop, const SharedPtr<FormatOptions>& format_options)
|
||||||
{
|
{
|
||||||
ASSERT(fop->seq.format_options == NULL);
|
ASSERT(fop->seq.format_options == NULL);
|
||||||
@ -782,6 +801,7 @@ static FileOp* fop_new(FileOpType type)
|
|||||||
|
|
||||||
fop->type = type;
|
fop->type = type;
|
||||||
fop->format = NULL;
|
fop->format = NULL;
|
||||||
|
fop->format_data = NULL;
|
||||||
fop->document = NULL;
|
fop->document = NULL;
|
||||||
|
|
||||||
fop->mutex = new Mutex();
|
fop->mutex = new Mutex();
|
||||||
|
@ -50,6 +50,7 @@ struct FileOp
|
|||||||
{
|
{
|
||||||
FileOpType type; // Operation type: 0=load, 1=save.
|
FileOpType type; // Operation type: 0=load, 1=save.
|
||||||
FileFormat* format;
|
FileFormat* format;
|
||||||
|
void* format_data; // Custom data for the FileFormat::onLoad/onSave operations.
|
||||||
Document* document; // Loaded document, or document to be saved.
|
Document* document; // Loaded document, or document to be saved.
|
||||||
std::string filename; // File-name to load/save.
|
std::string filename; // File-name to load/save.
|
||||||
|
|
||||||
@ -108,6 +109,9 @@ void fop_done(FileOp* fop);
|
|||||||
void fop_stop(FileOp* fop);
|
void fop_stop(FileOp* fop);
|
||||||
void fop_free(FileOp* fop);
|
void fop_free(FileOp* fop);
|
||||||
|
|
||||||
|
// Does extra post-load processing which may require user intervention.
|
||||||
|
void fop_post_load(FileOp* fop);
|
||||||
|
|
||||||
void fop_sequence_set_format_options(FileOp* fop, const SharedPtr<FormatOptions>& format_options);
|
void fop_sequence_set_format_options(FileOp* fop, const SharedPtr<FormatOptions>& format_options);
|
||||||
void fop_sequence_set_color(FileOp* fop, int index, int r, int g, int b);
|
void fop_sequence_set_color(FileOp* fop, int index, int r, int g, int b);
|
||||||
void fop_sequence_get_color(FileOp* fop, int index, int *r, int *g, int *b);
|
void fop_sequence_get_color(FileOp* fop, int index, int *r, int *g, int *b);
|
||||||
|
@ -52,3 +52,13 @@ bool FileFormat::save(FileOp* fop)
|
|||||||
ASSERT(support(FILE_SUPPORT_SAVE));
|
ASSERT(support(FILE_SUPPORT_SAVE));
|
||||||
return onSave(fop);
|
return onSave(fop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileFormat::postLoad(FileOp* fop)
|
||||||
|
{
|
||||||
|
return onPostLoad(fop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileFormat::destroyData(FileOp* fop)
|
||||||
|
{
|
||||||
|
onDestroyData(fop);
|
||||||
|
}
|
||||||
|
@ -54,6 +54,13 @@ public:
|
|||||||
bool load(FileOp* fop);
|
bool load(FileOp* fop);
|
||||||
bool save(FileOp* fop);
|
bool save(FileOp* fop);
|
||||||
|
|
||||||
|
// Does post-load operation which require user intervention.
|
||||||
|
// Returns false cancelled the operation.
|
||||||
|
bool postLoad(FileOp* fop);
|
||||||
|
|
||||||
|
// Destroys the custom data stored in "fop->format_data" field.
|
||||||
|
void destroyData(FileOp* fop);
|
||||||
|
|
||||||
// Returns extra options for this format. It can return != NULL
|
// Returns extra options for this format. It can return != NULL
|
||||||
// only if flags() returns FILE_SUPPORT_GET_FORMAT_OPTIONS.
|
// only if flags() returns FILE_SUPPORT_GET_FORMAT_OPTIONS.
|
||||||
SharedPtr<FormatOptions> getFormatOptions(FileOp* fop) {
|
SharedPtr<FormatOptions> getFormatOptions(FileOp* fop) {
|
||||||
@ -71,7 +78,9 @@ protected:
|
|||||||
virtual int onGetFlags() const = 0;
|
virtual int onGetFlags() const = 0;
|
||||||
|
|
||||||
virtual bool onLoad(FileOp* fop) = 0;
|
virtual bool onLoad(FileOp* fop) = 0;
|
||||||
|
virtual bool onPostLoad(FileOp* fop) { return true; }
|
||||||
virtual bool onSave(FileOp* fop) = 0;
|
virtual bool onSave(FileOp* fop) = 0;
|
||||||
|
virtual void onDestroyData(FileOp* fop) { }
|
||||||
|
|
||||||
virtual SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) {
|
virtual SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) {
|
||||||
return SharedPtr<FormatOptions>(0);
|
return SharedPtr<FormatOptions>(0);
|
||||||
|
@ -37,6 +37,34 @@ enum DisposalMethod {
|
|||||||
DISPOSAL_METHOD_RESTORE_PREVIOUS,
|
DISPOSAL_METHOD_RESTORE_PREVIOUS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct GifFrame
|
||||||
|
{
|
||||||
|
int x, y;
|
||||||
|
int duration;
|
||||||
|
int mask_index;
|
||||||
|
Image* image;
|
||||||
|
Palette* palette;
|
||||||
|
DisposalMethod disposal_method;
|
||||||
|
|
||||||
|
GifFrame()
|
||||||
|
: x(0), y(0)
|
||||||
|
, mask_index(-1)
|
||||||
|
, image(0)
|
||||||
|
, palette(0)
|
||||||
|
, disposal_method(DISPOSAL_METHOD_NONE) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<GifFrame> GifFrames;
|
||||||
|
|
||||||
|
struct GifData
|
||||||
|
{
|
||||||
|
int sprite_w;
|
||||||
|
int sprite_h;
|
||||||
|
int bgcolor_index;
|
||||||
|
GifFrames frames;
|
||||||
|
};
|
||||||
|
|
||||||
class GifFormat : public FileFormat
|
class GifFormat : public FileFormat
|
||||||
{
|
{
|
||||||
const char* onGetName() const { return "gif"; }
|
const char* onGetName() const { return "gif"; }
|
||||||
@ -55,6 +83,8 @@ class GifFormat : public FileFormat
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop);
|
bool onLoad(FileOp* fop);
|
||||||
|
bool onPostLoad(FileOp* fop) OVERRIDE;
|
||||||
|
void onDestroyData(FileOp* fop) OVERRIDE;
|
||||||
bool onSave(FileOp* fop);
|
bool onSave(FileOp* fop);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,29 +105,19 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sprite_w = gif_file->SWidth;
|
GifData* data = new GifData;
|
||||||
int sprite_h = gif_file->SHeight;
|
fop->format_data = reinterpret_cast<void*>(data);
|
||||||
|
|
||||||
|
data->sprite_w = gif_file->SWidth;
|
||||||
|
data->sprite_h = gif_file->SHeight;
|
||||||
|
|
||||||
// The previous image is used to support the special disposal method
|
|
||||||
// of GIF frames DISPOSAL_METHOD_RESTORE_PREVIOUS (number 3 in
|
|
||||||
// Graphics Extension)
|
|
||||||
UniquePtr<Image> current_image(image_new(IMAGE_RGB, sprite_w, sprite_h));
|
|
||||||
UniquePtr<Image> previous_image(image_new(IMAGE_RGB, sprite_w, sprite_h));
|
|
||||||
UniquePtr<Palette> current_palette(new Palette(0, 256));
|
UniquePtr<Palette> current_palette(new Palette(0, 256));
|
||||||
UniquePtr<Palette> previous_palette(new Palette(0, 256));
|
UniquePtr<Palette> previous_palette(new Palette(0, 256));
|
||||||
|
|
||||||
// Create the sprite with the GIF dimension
|
|
||||||
UniquePtr<Sprite> sprite(new Sprite(IMAGE_RGB, sprite_w, sprite_h, 256));
|
|
||||||
|
|
||||||
// Create the main layer
|
|
||||||
LayerImage* layer = new LayerImage(sprite);
|
|
||||||
sprite->getFolder()->add_layer(layer);
|
|
||||||
|
|
||||||
// If the GIF image has a global palette, it has a valid
|
// If the GIF image has a global palette, it has a valid
|
||||||
// background color (so the GIF is not transparent).
|
// background color (so the GIF is not transparent).
|
||||||
int bgcolor_index;
|
|
||||||
if (gif_file->SColorMap != NULL) {
|
if (gif_file->SColorMap != NULL) {
|
||||||
bgcolor_index = gif_file->SBackGroundColor;
|
data->bgcolor_index = gif_file->SBackGroundColor;
|
||||||
|
|
||||||
// Setup the first palette using the global color map.
|
// Setup the first palette using the global color map.
|
||||||
ColorMapObject* colormap = gif_file->SColorMap;
|
ColorMapObject* colormap = gif_file->SColorMap;
|
||||||
@ -108,16 +128,12 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bgcolor_index = 0;
|
data->bgcolor_index = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear both images with the transparent color (alpha = 0).
|
|
||||||
image_clear(current_image, _rgba(0, 0, 0, 0));
|
|
||||||
image_clear(previous_image, _rgba(0, 0, 0, 0));
|
|
||||||
|
|
||||||
// Scan the content of the GIF file (read record by record)
|
// Scan the content of the GIF file (read record by record)
|
||||||
GifRecordType record_type;
|
GifRecordType record_type;
|
||||||
int frame_num = 0;
|
size_t frame_num = 0;
|
||||||
DisposalMethod disposal_method = DISPOSAL_METHOD_NONE;
|
DisposalMethod disposal_method = DISPOSAL_METHOD_NONE;
|
||||||
int transparent_index = -1;
|
int transparent_index = -1;
|
||||||
int frame_delay = -1;
|
int frame_delay = -1;
|
||||||
@ -138,16 +154,20 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
int frame_h = gif_file->Image.Height;
|
int frame_h = gif_file->Image.Height;
|
||||||
|
|
||||||
if (frame_x < 0 || frame_y < 0 ||
|
if (frame_x < 0 || frame_y < 0 ||
|
||||||
frame_x + frame_w > sprite_w ||
|
frame_x + frame_w > data->sprite_w ||
|
||||||
frame_y + frame_h > sprite_h)
|
frame_y + frame_h > data->sprite_h)
|
||||||
throw base::Exception("Image %d is out of sprite bounds.\n", frame_num);
|
throw base::Exception("Image %d is out of sprite bounds.\n", frame_num);
|
||||||
|
|
||||||
// Add a new frame in the sprite.
|
// Add a new frames.
|
||||||
sprite->setTotalFrames(frame_num+1);
|
if (frame_num >= data->frames.size())
|
||||||
|
data->frames.resize(frame_num+1);
|
||||||
|
|
||||||
|
data->frames[frame_num].x = frame_x;
|
||||||
|
data->frames[frame_num].y = frame_y;
|
||||||
|
|
||||||
// Set frame delay (1/100th seconds to milliseconds)
|
// Set frame delay (1/100th seconds to milliseconds)
|
||||||
if (frame_delay >= 0)
|
if (frame_delay >= 0)
|
||||||
sprite->setFrameDuration(frame_num, frame_delay*10);
|
data->frames[frame_num].duration = frame_delay*10;
|
||||||
|
|
||||||
// Update palette for this frame (the first frame always need a palette).
|
// Update palette for this frame (the first frame always need a palette).
|
||||||
if (gif_file->Image.ColorMap) {
|
if (gif_file->Image.ColorMap) {
|
||||||
@ -161,7 +181,9 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
if (frame_num == 0 || previous_palette->countDiff(current_palette, NULL, NULL)) {
|
if (frame_num == 0 || previous_palette->countDiff(current_palette, NULL, NULL)) {
|
||||||
current_palette->setFrame(frame_num);
|
current_palette->setFrame(frame_num);
|
||||||
sprite->setPalette(current_palette, true);
|
|
||||||
|
data->frames[frame_num].palette = new Palette(*current_palette);
|
||||||
|
data->frames[frame_num].palette->setFrame(frame_num);
|
||||||
|
|
||||||
current_palette->copyColorsTo(previous_palette);
|
current_palette->copyColorsTo(previous_palette);
|
||||||
}
|
}
|
||||||
@ -187,68 +209,12 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the indexed image to RGB
|
// Detach the pointer of the frame-image and put it in the list of frames.
|
||||||
for (int y = 0; y < frame_h; ++y)
|
data->frames[frame_num].image = frame_image.release();
|
||||||
for (int x = 0; x < frame_w; ++x) {
|
data->frames[frame_num].disposal_method = disposal_method;
|
||||||
int pixel_index = image_getpixel_fast<IndexedTraits>(frame_image, x, y);
|
data->frames[frame_num].mask_index = transparent_index;
|
||||||
if (pixel_index != transparent_index)
|
|
||||||
image_putpixel_fast<RgbTraits>(current_image,
|
|
||||||
frame_x + x,
|
|
||||||
frame_y + y,
|
|
||||||
current_palette->getEntry(pixel_index));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new Cel and a image with the whole content of "current_image"
|
PRINTF("Frame[%d] transparent index = %d\n", frame_num, transparent_index);
|
||||||
Cel* cel = new Cel(frame_num, 0);
|
|
||||||
try {
|
|
||||||
Image* cel_image = image_new_copy(current_image);
|
|
||||||
try {
|
|
||||||
// Add the image in the sprite's stock and update the cel's
|
|
||||||
// reference to the new stock's image.
|
|
||||||
cel->setImage(sprite->getStock()->addImage(cel_image));
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
delete cel_image;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer->addCel(cel);
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
delete cel;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The current_image was already copied to represent the
|
|
||||||
// current frame (frame_num), so now we have to clear the
|
|
||||||
// area occupied by frame_image using the desired disposal
|
|
||||||
// method.
|
|
||||||
switch (disposal_method) {
|
|
||||||
|
|
||||||
case DISPOSAL_METHOD_NONE:
|
|
||||||
case DISPOSAL_METHOD_DO_NOT_DISPOSE:
|
|
||||||
// Do nothing
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DISPOSAL_METHOD_RESTORE_BGCOLOR:
|
|
||||||
image_rectfill(current_image,
|
|
||||||
frame_x, frame_y,
|
|
||||||
frame_x+frame_w-1,
|
|
||||||
frame_y+frame_h-1,
|
|
||||||
_rgba(0, 0, 0, 0));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DISPOSAL_METHOD_RESTORE_PREVIOUS:
|
|
||||||
image_copy(current_image, previous_image, 0, 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update previous_image with current_image only if the
|
|
||||||
// disposal method is not "restore previous" (which means
|
|
||||||
// that we have already updated current_image from
|
|
||||||
// previous_image).
|
|
||||||
if (disposal_method != DISPOSAL_METHOD_RESTORE_PREVIOUS)
|
|
||||||
image_copy(previous_image, current_image, 0, 0);
|
|
||||||
|
|
||||||
++frame_num;
|
++frame_num;
|
||||||
|
|
||||||
@ -298,12 +264,222 @@ bool GifFormat::onLoad(FileOp* fop)
|
|||||||
break;
|
break;
|
||||||
} while (record_type != TERMINATE_RECORD_TYPE);
|
} while (record_type != TERMINATE_RECORD_TYPE);
|
||||||
|
|
||||||
fop->document = new Document(sprite);
|
fop->document = new Document(NULL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GifFormat::onPostLoad(FileOp* fop)
|
||||||
|
{
|
||||||
|
GifData* data = reinterpret_cast<GifData*>(fop->format_data);
|
||||||
|
if (!data)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
int imgtype = IMAGE_INDEXED;
|
||||||
|
bool askForConversion = false;
|
||||||
|
|
||||||
|
if (!fop->oneframe) {
|
||||||
|
int global_mask_index = -1;
|
||||||
|
|
||||||
|
for (GifFrames::iterator
|
||||||
|
frame_it=data->frames.begin(),
|
||||||
|
frame_end=data->frames.end(); frame_it != frame_end; ++frame_it) {
|
||||||
|
|
||||||
|
// Convert the indexed image to RGB
|
||||||
|
for (int y=0; y<frame_it->image->h; ++y) {
|
||||||
|
for (int x=0; x<frame_it->image->w; ++x) {
|
||||||
|
int pixel_index = image_getpixel_fast<IndexedTraits>(frame_it->image, x, y);
|
||||||
|
|
||||||
|
if (pixel_index >= 0 && pixel_index < 256) {
|
||||||
|
// This pixel matches the frame's transparent color
|
||||||
|
if (pixel_index == frame_it->mask_index) {
|
||||||
|
// If we haven't set a background color yet, this is our new background color.
|
||||||
|
if (global_mask_index < 0) {
|
||||||
|
global_mask_index = pixel_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Drawing the mask color
|
||||||
|
if (global_mask_index == pixel_index) {
|
||||||
|
askForConversion = true;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New background color
|
||||||
|
data->bgcolor_index = global_mask_index;
|
||||||
|
|
||||||
|
done:;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (askForConversion) {
|
||||||
|
int result =
|
||||||
|
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."
|
||||||
|
"<<What would you like to do?"
|
||||||
|
"||Convert to &RGBA||Keep &Indexed||&Cancel",
|
||||||
|
fop->document->getFilename());
|
||||||
|
|
||||||
|
if (result == 1)
|
||||||
|
imgtype = IMAGE_RGB;
|
||||||
|
else if (result != 2)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the sprite with the GIF dimension
|
||||||
|
UniquePtr<Sprite> sprite(new Sprite(imgtype, data->sprite_w, data->sprite_h, 256));
|
||||||
|
|
||||||
|
// Create the main layer
|
||||||
|
LayerImage* layer = new LayerImage(sprite);
|
||||||
|
sprite->getFolder()->add_layer(layer);
|
||||||
|
|
||||||
|
if (imgtype == IMAGE_INDEXED) {
|
||||||
|
if (data->bgcolor_index >= 0)
|
||||||
|
sprite->setTransparentColor(data->bgcolor_index);
|
||||||
|
else
|
||||||
|
layer->configureAsBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The previous image is used to support the special disposal method
|
||||||
|
// of GIF frames DISPOSAL_METHOD_RESTORE_PREVIOUS (number 3 in
|
||||||
|
// Graphics Extension)
|
||||||
|
UniquePtr<Image> current_image(image_new(imgtype, data->sprite_w, data->sprite_h));
|
||||||
|
UniquePtr<Image> previous_image(image_new(imgtype, data->sprite_w, data->sprite_h));
|
||||||
|
|
||||||
|
// Clear both images with the transparent color (alpha = 0).
|
||||||
|
uint32_t bgcolor = (imgtype == IMAGE_RGB ? _rgba(0, 0, 0, 0):
|
||||||
|
(data->bgcolor_index >= 0 ? data->bgcolor_index: 0));
|
||||||
|
image_clear(current_image, bgcolor);
|
||||||
|
image_clear(previous_image, bgcolor);
|
||||||
|
|
||||||
|
// Add all frames in the sprite.
|
||||||
|
sprite->setTotalFrames(data->frames.size());
|
||||||
|
Palette* current_palette = NULL;
|
||||||
|
|
||||||
|
size_t frame_num = 0;
|
||||||
|
for (GifFrames::iterator
|
||||||
|
frame_it=data->frames.begin(),
|
||||||
|
frame_end=data->frames.end(); frame_it != frame_end; ++frame_it, ++frame_num) {
|
||||||
|
|
||||||
|
// Set frame duration
|
||||||
|
sprite->setFrameDuration(frame_num, frame_it->duration);
|
||||||
|
|
||||||
|
// Set frame palette
|
||||||
|
if (frame_it->palette) {
|
||||||
|
sprite->setPalette(frame_it->palette, true);
|
||||||
|
current_palette = frame_it->palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (imgtype) {
|
||||||
|
|
||||||
|
case IMAGE_INDEXED:
|
||||||
|
for (int y = 0; y < frame_it->image->h; ++y)
|
||||||
|
for (int x = 0; x < frame_it->image->w; ++x) {
|
||||||
|
int pixel_index = image_getpixel_fast<IndexedTraits>(frame_it->image, x, y);
|
||||||
|
if (pixel_index != frame_it->mask_index)
|
||||||
|
image_putpixel_fast<IndexedTraits>(current_image,
|
||||||
|
frame_it->x + x,
|
||||||
|
frame_it->y + y,
|
||||||
|
pixel_index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IMAGE_RGB:
|
||||||
|
// Convert the indexed image to RGB
|
||||||
|
for (int y = 0; y < frame_it->image->h; ++y)
|
||||||
|
for (int x = 0; x < frame_it->image->w; ++x) {
|
||||||
|
int pixel_index = image_getpixel_fast<IndexedTraits>(frame_it->image, x, y);
|
||||||
|
if (pixel_index != frame_it->mask_index)
|
||||||
|
image_putpixel_fast<RgbTraits>(current_image,
|
||||||
|
frame_it->x + x,
|
||||||
|
frame_it->y + y,
|
||||||
|
current_palette->getEntry(pixel_index));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Cel and a image with the whole content of "current_image"
|
||||||
|
Cel* cel = new Cel(frame_num, 0);
|
||||||
|
try {
|
||||||
|
Image* cel_image = image_new_copy(current_image);
|
||||||
|
try {
|
||||||
|
// Add the image in the sprite's stock and update the cel's
|
||||||
|
// reference to the new stock's image.
|
||||||
|
cel->setImage(sprite->getStock()->addImage(cel_image));
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
delete cel_image;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer->addCel(cel);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
delete cel;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current_image was already copied to represent the
|
||||||
|
// current frame (frame_num), so now we have to clear the
|
||||||
|
// area occupied by frame_image using the desired disposal
|
||||||
|
// method.
|
||||||
|
switch (frame_it->disposal_method) {
|
||||||
|
|
||||||
|
case DISPOSAL_METHOD_NONE:
|
||||||
|
case DISPOSAL_METHOD_DO_NOT_DISPOSE:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DISPOSAL_METHOD_RESTORE_BGCOLOR:
|
||||||
|
image_rectfill(current_image,
|
||||||
|
frame_it->x,
|
||||||
|
frame_it->y,
|
||||||
|
frame_it->x+frame_it->image->w-1,
|
||||||
|
frame_it->y+frame_it->image->h-1,
|
||||||
|
bgcolor);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DISPOSAL_METHOD_RESTORE_PREVIOUS:
|
||||||
|
image_copy(current_image, previous_image, 0, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update previous_image with current_image only if the
|
||||||
|
// disposal method is not "restore previous" (which means
|
||||||
|
// that we have already updated current_image from
|
||||||
|
// previous_image).
|
||||||
|
if (frame_it->disposal_method != DISPOSAL_METHOD_RESTORE_PREVIOUS)
|
||||||
|
image_copy(previous_image, current_image, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fop->document->addSprite(sprite);
|
||||||
sprite.release(); // Now the sprite is owned by fop->document
|
sprite.release(); // Now the sprite is owned by fop->document
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GifFormat::onDestroyData(FileOp* fop)
|
||||||
|
{
|
||||||
|
GifData* data = reinterpret_cast<GifData*>(fop->format_data);
|
||||||
|
if (data) {
|
||||||
|
GifFrames::iterator frame_it = data->frames.begin();
|
||||||
|
GifFrames::iterator frame_end = data->frames.end();
|
||||||
|
|
||||||
|
for (; frame_it != frame_end; ++frame_it) {
|
||||||
|
delete frame_it->image;
|
||||||
|
delete frame_it->palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool GifFormat::onSave(FileOp* fop)
|
bool GifFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
UniquePtr<GifFileType, int(*)(GifFileType*)> gif_file(EGifOpenFileName(fop->filename.c_str(), 0),
|
UniquePtr<GifFileType, int(*)(GifFileType*)> gif_file(EGifOpenFileName(fop->filename.c_str(), 0),
|
||||||
@ -315,10 +491,10 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
int sprite_w = sprite->getWidth();
|
int sprite_w = sprite->getWidth();
|
||||||
int sprite_h = sprite->getHeight();
|
int sprite_h = sprite->getHeight();
|
||||||
int sprite_imgtype = sprite->getImgType();
|
int sprite_imgtype = sprite->getImgType();
|
||||||
int background_color = 0;
|
|
||||||
bool interlace = false;
|
bool interlace = false;
|
||||||
int loop = 0;
|
int loop = 0;
|
||||||
int transparent_index = (sprite->getBackgroundLayer() == NULL) ? 0: -1;
|
int background_color = (sprite_imgtype == IMAGE_INDEXED ? sprite->getTransparentColor(): 0);
|
||||||
|
int transparent_index = (sprite->getBackgroundLayer() ? -1: sprite->getTransparentColor());
|
||||||
|
|
||||||
Palette* current_palette = sprite->getPalette(0);
|
Palette* current_palette = sprite->getPalette(0);
|
||||||
Palette* previous_palette = current_palette;
|
Palette* previous_palette = current_palette;
|
||||||
@ -390,7 +566,7 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
// If the sprite is Indexed, we can render directly into "current_image".
|
// If the sprite is Indexed, we can render directly into "current_image".
|
||||||
else {
|
else {
|
||||||
image_clear(current_image, 0);
|
image_clear(current_image, background_color);
|
||||||
layer_render(sprite->getFolder(), current_image, 0, 0, frame_num);
|
layer_render(sprite->getFolder(), current_image, 0, 0, frame_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,14 +611,15 @@ bool GifFormat::onSave(FileOp* fop)
|
|||||||
// frame and maybe the transparency index).
|
// frame and maybe the transparency index).
|
||||||
{
|
{
|
||||||
unsigned char extension_bytes[5];
|
unsigned char extension_bytes[5];
|
||||||
int disposal_method = (sprite->getBackgroundLayer() == NULL) ? 2: 1;
|
int disposal_method = (sprite->getBackgroundLayer() ? DISPOSAL_METHOD_DO_NOT_DISPOSE:
|
||||||
|
DISPOSAL_METHOD_RESTORE_BGCOLOR);
|
||||||
int frame_delay = sprite->getFrameDuration(frame_num) / 10;
|
int frame_delay = sprite->getFrameDuration(frame_num) / 10;
|
||||||
|
|
||||||
extension_bytes[0] = (((disposal_method & 7) << 2) |
|
extension_bytes[0] = (((disposal_method & 7) << 2) |
|
||||||
(transparent_index >= 0 ? 1: 0));
|
(transparent_index >= 0 ? 1: 0));
|
||||||
extension_bytes[1] = (frame_delay & 0xff);
|
extension_bytes[1] = (frame_delay & 0xff);
|
||||||
extension_bytes[2] = (frame_delay >> 8) & 0xff;
|
extension_bytes[2] = (frame_delay >> 8) & 0xff;
|
||||||
extension_bytes[3] = transparent_index;
|
extension_bytes[3] = (transparent_index >= 0 ? transparent_index: 0);
|
||||||
|
|
||||||
if (EGifPutExtension(gif_file, GRAPHICS_EXT_FUNC_CODE, 4, extension_bytes) == GIF_ERROR)
|
if (EGifPutExtension(gif_file, GRAPHICS_EXT_FUNC_CODE, 4, extension_bytes) == GIF_ERROR)
|
||||||
throw base::Exception("Error writing GIF graphics extension record for frame %d.\n", frame_num);
|
throw base::Exception("Error writing GIF graphics extension record for frame %d.\n", frame_num);
|
||||||
|
@ -364,6 +364,8 @@ void layer_render(const Layer* layer, Image* image, int x, int y, int frame)
|
|||||||
src_image = layer->getSprite()->getStock()->getImage(cel->getImage());
|
src_image = layer->getSprite()->getStock()->getImage(cel->getImage());
|
||||||
ASSERT(src_image != NULL);
|
ASSERT(src_image != NULL);
|
||||||
|
|
||||||
|
src_image->mask_color = layer->getSprite()->getTransparentColor();
|
||||||
|
|
||||||
image_merge(image, src_image,
|
image_merge(image, src_image,
|
||||||
cel->getX() + x,
|
cel->getX() + x,
|
||||||
cel->getY() + y,
|
cel->getY() + y,
|
||||||
|
@ -392,7 +392,7 @@ Image* RenderEngine::renderSprite(const Document* document,
|
|||||||
case IMAGE_INDEXED:
|
case IMAGE_INDEXED:
|
||||||
zoomed_func = merge_zoomed_image<RgbTraits, IndexedTraits>;
|
zoomed_func = merge_zoomed_image<RgbTraits, IndexedTraits>;
|
||||||
if (!need_checked_bg)
|
if (!need_checked_bg)
|
||||||
bg_color = sprite->getPalette(frame)->getEntry(0);
|
bg_color = sprite->getPalette(frame)->getEntry(sprite->getTransparentColor());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -571,7 +571,7 @@ void RenderEngine::renderLayer(const Document* document,
|
|||||||
|
|
||||||
const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame);
|
const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame);
|
||||||
if (cel != NULL) {
|
if (cel != NULL) {
|
||||||
const Image* src_image;
|
Image* src_image;
|
||||||
|
|
||||||
/* is the 'rastering_image' setted to be used with this layer? */
|
/* is the 'rastering_image' setted to be used with this layer? */
|
||||||
if ((frame == sprite->getCurrentFrame()) &&
|
if ((frame == sprite->getCurrentFrame()) &&
|
||||||
@ -593,6 +593,8 @@ void RenderEngine::renderLayer(const Document* document,
|
|||||||
output_opacity = MID(0, cel->getOpacity(), 255);
|
output_opacity = MID(0, cel->getOpacity(), 255);
|
||||||
output_opacity = INT_MULT(output_opacity, global_opacity, t);
|
output_opacity = INT_MULT(output_opacity, global_opacity, t);
|
||||||
|
|
||||||
|
src_image->mask_color = sprite->getTransparentColor();
|
||||||
|
|
||||||
(*zoomed_func)(image, src_image, sprite->getPalette(frame),
|
(*zoomed_func)(image, src_image, sprite->getPalette(frame),
|
||||||
(cel->getX() << zoom) - source_x,
|
(cel->getX() << zoom) - source_x,
|
||||||
(cel->getY() << zoom) - source_y,
|
(cel->getY() << zoom) - source_y,
|
||||||
|
@ -755,12 +755,38 @@ static void fileview_stop_threads(FileView* fileview)
|
|||||||
static void openfile_bg(ThumbnailData* data)
|
static void openfile_bg(ThumbnailData* data)
|
||||||
{
|
{
|
||||||
FileOp* fop = (FileOp*)data->fop;
|
FileOp* fop = (FileOp*)data->fop;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fop_operate(fop);
|
||||||
|
}
|
||||||
|
catch (const std::exception& e) {
|
||||||
|
fop_error(fop, "Error loading file:\n%s", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
fop_done(fop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the GUI-monitor (a timer in the gui module that is called
|
||||||
|
* every 100 milliseconds).
|
||||||
|
*
|
||||||
|
* [main thread]
|
||||||
|
*/
|
||||||
|
static void monitor_thumbnail_generation(void *_data)
|
||||||
|
{
|
||||||
|
ThumbnailData* data = (ThumbnailData*)_data;
|
||||||
|
FileOp* fop = data->fop;
|
||||||
|
|
||||||
|
/* is done? ...ok, now the thumbnail is in the main thread only... */
|
||||||
|
if (fop_is_done(fop)) {
|
||||||
|
// Post load
|
||||||
|
fop_post_load(fop);
|
||||||
|
|
||||||
|
// Convert the loaded document into the Allegro bitmap "data->thumbnail".
|
||||||
|
{
|
||||||
int thumb_w, thumb_h;
|
int thumb_w, thumb_h;
|
||||||
Image* image;
|
Image* image;
|
||||||
|
|
||||||
// Load the file.
|
|
||||||
fop_operate(fop);
|
|
||||||
|
|
||||||
Sprite* sprite = (fop->document && fop->document->getSprite()) ? fop->document->getSprite():
|
Sprite* sprite = (fop->document && fop->document->getSprite()) ? fop->document->getSprite():
|
||||||
NULL;
|
NULL;
|
||||||
if (!fop_is_stop(fop) && sprite) {
|
if (!fop_is_stop(fop) && sprite) {
|
||||||
@ -790,22 +816,8 @@ static void openfile_bg(ThumbnailData* data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete fop->document;
|
delete fop->document;
|
||||||
fop_done(fop);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the GUI-monitor (a timer in the gui module that is called
|
|
||||||
* every 100 milliseconds).
|
|
||||||
*
|
|
||||||
* [main thread]
|
|
||||||
*/
|
|
||||||
static void monitor_thumbnail_generation(void *_data)
|
|
||||||
{
|
|
||||||
ThumbnailData* data = (ThumbnailData*)_data;
|
|
||||||
FileOp* fop = data->fop;
|
|
||||||
|
|
||||||
/* is done? ...ok, now the thumbnail is in the main thread only... */
|
|
||||||
if (fop_is_done(fop)) {
|
|
||||||
/* set the thumbnail of the file-item */
|
/* set the thumbnail of the file-item */
|
||||||
if (data->thumbnail) {
|
if (data->thumbnail) {
|
||||||
BITMAP *bmp = create_bitmap_ex(16,
|
BITMAP *bmp = create_bitmap_ex(16,
|
||||||
|
Loading…
Reference in New Issue
Block a user