mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-28 16:20:50 +00:00
671 lines
24 KiB
C++
671 lines
24 KiB
C++
/* ASEPRITE
|
|
* Copyright (C) 2001-2012 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 "config.h"
|
|
|
|
#include "base/unique_ptr.h"
|
|
#include "document.h"
|
|
#include "file/file.h"
|
|
#include "file/file_format.h"
|
|
#include "file/format_options.h"
|
|
#include "modules/gui.h"
|
|
#include "raster/raster.h"
|
|
#include "ui/alert.h"
|
|
#include "util/autocrop.h"
|
|
|
|
#include <gif_lib.h>
|
|
|
|
enum DisposalMethod {
|
|
DISPOSAL_METHOD_NONE,
|
|
DISPOSAL_METHOD_DO_NOT_DISPOSE,
|
|
DISPOSAL_METHOD_RESTORE_BGCOLOR,
|
|
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
|
|
{
|
|
const char* onGetName() const { return "gif"; }
|
|
const char* onGetExtensions() const { return "gif"; }
|
|
int onGetFlags() const {
|
|
return
|
|
FILE_SUPPORT_LOAD |
|
|
FILE_SUPPORT_SAVE |
|
|
FILE_SUPPORT_RGB |
|
|
FILE_SUPPORT_RGBA |
|
|
FILE_SUPPORT_GRAY |
|
|
FILE_SUPPORT_GRAYA |
|
|
FILE_SUPPORT_INDEXED |
|
|
FILE_SUPPORT_FRAMES |
|
|
FILE_SUPPORT_PALETTES;
|
|
}
|
|
|
|
bool onLoad(FileOp* fop);
|
|
bool onPostLoad(FileOp* fop) OVERRIDE;
|
|
void onDestroyData(FileOp* fop) OVERRIDE;
|
|
bool onSave(FileOp* fop);
|
|
};
|
|
|
|
FileFormat* CreateGifFormat()
|
|
{
|
|
return new GifFormat;
|
|
}
|
|
|
|
static int interlaced_offset[] = { 0, 4, 2, 1 };
|
|
static int interlaced_jumps[] = { 8, 8, 4, 2 };
|
|
|
|
bool GifFormat::onLoad(FileOp* fop)
|
|
{
|
|
UniquePtr<GifFileType, int(*)(GifFileType*)> gif_file(DGifOpenFileName(fop->filename.c_str()),
|
|
DGifCloseFile);
|
|
if (!gif_file) {
|
|
fop_error(fop, "Error loading GIF header.\n");
|
|
return false;
|
|
}
|
|
|
|
GifData* data = new GifData;
|
|
fop->format_data = reinterpret_cast<void*>(data);
|
|
|
|
data->sprite_w = gif_file->SWidth;
|
|
data->sprite_h = gif_file->SHeight;
|
|
|
|
UniquePtr<Palette> current_palette(new Palette(FrameNumber(0), 256));
|
|
UniquePtr<Palette> previous_palette(new Palette(FrameNumber(0), 256));
|
|
|
|
// If the GIF image has a global palette, it has a valid
|
|
// background color (so the GIF is not transparent).
|
|
if (gif_file->SColorMap != NULL) {
|
|
data->bgcolor_index = gif_file->SBackGroundColor;
|
|
|
|
// Setup the first palette using the global color map.
|
|
ColorMapObject* colormap = gif_file->SColorMap;
|
|
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));
|
|
}
|
|
}
|
|
else {
|
|
data->bgcolor_index = -1;
|
|
}
|
|
|
|
// Scan the content of the GIF file (read record by record)
|
|
GifRecordType record_type;
|
|
FrameNumber frame_num(0);
|
|
DisposalMethod disposal_method = DISPOSAL_METHOD_NONE;
|
|
int transparent_index = -1;
|
|
int frame_delay = -1;
|
|
do {
|
|
if (DGifGetRecordType(gif_file, &record_type) == GIF_ERROR)
|
|
throw base::Exception("Invalid GIF record in file.\n");
|
|
|
|
switch (record_type) {
|
|
|
|
case IMAGE_DESC_RECORD_TYPE: {
|
|
if (DGifGetImageDesc(gif_file) == GIF_ERROR)
|
|
throw base::Exception("Invalid GIF image descriptor.\n");
|
|
|
|
// These are the bounds of the image to read.
|
|
int frame_x = gif_file->Image.Left;
|
|
int frame_y = gif_file->Image.Top;
|
|
int frame_w = gif_file->Image.Width;
|
|
int frame_h = gif_file->Image.Height;
|
|
|
|
if (frame_x < 0 || frame_y < 0 ||
|
|
frame_x + frame_w > data->sprite_w ||
|
|
frame_y + frame_h > data->sprite_h)
|
|
throw base::Exception("Image %d is out of sprite bounds.\n", (int)frame_num);
|
|
|
|
// Add a new frames.
|
|
if (frame_num >= FrameNumber(data->frames.size()))
|
|
data->frames.resize(frame_num.next());
|
|
|
|
data->frames[frame_num].x = frame_x;
|
|
data->frames[frame_num].y = frame_y;
|
|
|
|
// Set frame delay (1/100th seconds to milliseconds)
|
|
if (frame_delay >= 0)
|
|
data->frames[frame_num].duration = frame_delay*10;
|
|
|
|
// Update palette for this frame (the first frame always need a palette).
|
|
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));
|
|
}
|
|
}
|
|
|
|
if (frame_num == 0 || previous_palette->countDiff(current_palette, NULL, NULL)) {
|
|
current_palette->setFrame(frame_num);
|
|
|
|
data->frames[frame_num].palette = new Palette(*current_palette);
|
|
data->frames[frame_num].palette->setFrame(frame_num);
|
|
|
|
current_palette->copyColorsTo(previous_palette);
|
|
}
|
|
|
|
// Create a temporary image to load frame pixels.
|
|
UniquePtr<Image> frame_image(Image::create(IMAGE_INDEXED, frame_w, frame_h));
|
|
IndexedTraits::address_t addr;
|
|
|
|
if (gif_file->Image.Interlace) {
|
|
// 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]) {
|
|
addr = image_address_fast<IndexedTraits>(frame_image, 0, y);
|
|
if (DGifGetLine(gif_file, addr, frame_w) == GIF_ERROR)
|
|
throw base::Exception("Invalid interlaced image data.");
|
|
}
|
|
}
|
|
else {
|
|
for (int y = 0; y < frame_h; ++y) {
|
|
addr = image_address_fast<IndexedTraits>(frame_image, 0, y);
|
|
if (DGifGetLine(gif_file, addr, frame_w) == GIF_ERROR)
|
|
throw base::Exception("Invalid image data (%d).\n", GifLastError());
|
|
}
|
|
}
|
|
|
|
// Detach the pointer of the frame-image and put it in the list of frames.
|
|
data->frames[frame_num].image = frame_image.release();
|
|
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);
|
|
|
|
++frame_num;
|
|
|
|
disposal_method = DISPOSAL_METHOD_NONE;
|
|
transparent_index = -1;
|
|
frame_delay = -1;
|
|
break;
|
|
}
|
|
|
|
case EXTENSION_RECORD_TYPE: {
|
|
GifByteType* extension;
|
|
int ext_code;
|
|
|
|
if (DGifGetExtension(gif_file, &ext_code, &extension) == GIF_ERROR)
|
|
throw base::Exception("Invalid GIF extension record.\n");
|
|
|
|
if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
|
|
if (extension[0] >= 4) {
|
|
disposal_method = (DisposalMethod)((extension[1] >> 2) & 7);
|
|
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);
|
|
}
|
|
}
|
|
|
|
while (extension != NULL) {
|
|
if (DGifGetExtensionNext(gif_file, &extension) == GIF_ERROR)
|
|
throw base::Exception("Invalid GIF extension record.\n");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TERMINATE_RECORD_TYPE:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Just one frame?
|
|
if (frame_num > 0 && fop->oneframe)
|
|
break;
|
|
|
|
if (fop_is_stop(fop))
|
|
break;
|
|
} while (record_type != TERMINATE_RECORD_TYPE);
|
|
|
|
fop->document = new Document(NULL);
|
|
return true;
|
|
}
|
|
|
|
bool GifFormat::onPostLoad(FileOp* fop)
|
|
{
|
|
GifData* data = reinterpret_cast<GifData*>(fop->format_data);
|
|
if (!data)
|
|
return true;
|
|
|
|
PixelFormat pixelFormat = 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 =
|
|
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."
|
|
"<<What would you like to do?"
|
|
"||Convert to &RGBA||Keep &Indexed||&Cancel",
|
|
fop->document->getFilename());
|
|
|
|
if (result == 1)
|
|
pixelFormat = IMAGE_RGB;
|
|
else if (result != 2)
|
|
return false;
|
|
}
|
|
|
|
// Create the sprite with the GIF dimension
|
|
UniquePtr<Sprite> sprite(new Sprite(pixelFormat, data->sprite_w, data->sprite_h, 256));
|
|
|
|
// Create the main layer
|
|
LayerImage* layer = new LayerImage(sprite);
|
|
sprite->getFolder()->addLayer(layer);
|
|
|
|
if (pixelFormat == 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::create(pixelFormat, data->sprite_w, data->sprite_h));
|
|
UniquePtr<Image> previous_image(Image::create(pixelFormat, data->sprite_w, data->sprite_h));
|
|
|
|
// Clear both images with the transparent color (alpha = 0).
|
|
uint32_t bgcolor = (pixelFormat == 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(FrameNumber(data->frames.size()));
|
|
Palette* current_palette = NULL;
|
|
|
|
FrameNumber 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 (pixelFormat) {
|
|
|
|
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::createCopy(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
|
|
|
|
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)
|
|
{
|
|
UniquePtr<GifFileType, int(*)(GifFileType*)> gif_file(EGifOpenFileName(fop->filename.c_str(), 0),
|
|
EGifCloseFile);
|
|
if (!gif_file)
|
|
throw base::Exception("Error creating GIF file.\n");
|
|
|
|
Sprite* sprite = fop->document->getSprite();
|
|
int sprite_w = sprite->getWidth();
|
|
int sprite_h = sprite->getHeight();
|
|
PixelFormat sprite_format = sprite->getPixelFormat();
|
|
bool interlace = false;
|
|
int loop = 0;
|
|
int background_color = (sprite_format == IMAGE_INDEXED ? sprite->getTransparentColor(): 0);
|
|
int transparent_index = (sprite->getBackgroundLayer() ? -1: sprite->getTransparentColor());
|
|
|
|
Palette* current_palette = sprite->getPalette(FrameNumber(0));
|
|
Palette* previous_palette = current_palette;
|
|
ColorMapObject* color_map = MakeMapObject(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));
|
|
}
|
|
|
|
if (EGifPutScreenDesc(gif_file, sprite_w, sprite_h,
|
|
color_map->BitsPerPixel,
|
|
background_color, color_map) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF header.\n");
|
|
|
|
UniquePtr<Image> buffer_image;
|
|
UniquePtr<Image> current_image(Image::create(IMAGE_INDEXED, sprite_w, sprite_h));
|
|
UniquePtr<Image> previous_image(Image::create(IMAGE_INDEXED, sprite_w, sprite_h));
|
|
int frame_x, frame_y, frame_w, frame_h;
|
|
int u1, v1, u2, v2;
|
|
int i1, j1, i2, j2;
|
|
|
|
// If the sprite is not Indexed type, we will need a temporary
|
|
// buffer to render the full RGB or Grayscale sprite.
|
|
if (sprite_format != IMAGE_INDEXED)
|
|
buffer_image.reset(Image::create(sprite_format, sprite_w, sprite_h));
|
|
|
|
image_clear(current_image, background_color);
|
|
image_clear(previous_image, background_color);
|
|
|
|
for (FrameNumber frame_num(0); frame_num<sprite->getTotalFrames(); ++frame_num) {
|
|
current_palette = sprite->getPalette(frame_num);
|
|
|
|
// If the sprite is RGB or Grayscale, we must to convert it to Indexed on the fly.
|
|
if (sprite_format != IMAGE_INDEXED) {
|
|
image_clear(buffer_image, 0);
|
|
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 = image_getpixel_fast<RgbTraits>(buffer_image, x, y);
|
|
image_putpixel_fast<IndexedTraits>(current_image, x, y,
|
|
(_rgba_geta(pixel_value) >= 128) ?
|
|
current_palette->findBestfit(_rgba_getr(pixel_value),
|
|
_rgba_getg(pixel_value),
|
|
_rgba_getb(pixel_value)):
|
|
transparent_index);
|
|
}
|
|
break;
|
|
|
|
// 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 = image_getpixel_fast<GrayscaleTraits>(buffer_image, x, y);
|
|
image_putpixel_fast<IndexedTraits>(current_image, x, y,
|
|
(_graya_geta(pixel_value) >= 128) ?
|
|
current_palette->findBestfit(_graya_getv(pixel_value),
|
|
_graya_getv(pixel_value),
|
|
_graya_getv(pixel_value)):
|
|
transparent_index);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// If the sprite is Indexed, we can render directly into "current_image".
|
|
else {
|
|
image_clear(current_image, background_color);
|
|
layer_render(sprite->getFolder(), current_image, 0, 0, frame_num);
|
|
}
|
|
|
|
if (frame_num == 0) {
|
|
frame_x = 0;
|
|
frame_y = 0;
|
|
frame_w = sprite->getWidth();
|
|
frame_h = sprite->getHeight();
|
|
}
|
|
else {
|
|
// Get the rectangle where start differences with the previous frame.
|
|
if (get_shrink_rect2(&u1, &v1, &u2, &v2, current_image, previous_image)) {
|
|
// Check the minimal area with the background color.
|
|
if (get_shrink_rect(&i1, &j1, &i2, &j2, current_image, background_color)) {
|
|
frame_x = MIN(u1, i1);
|
|
frame_y = MIN(v1, j1);
|
|
frame_w = MAX(u2, i2) - MIN(u1, i1) + 1;
|
|
frame_h = MAX(v2, j2) - MIN(v1, j1) + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Specify loop extension.
|
|
if (frame_num == 0 && loop >= 0) {
|
|
unsigned char extension_bytes[11];
|
|
|
|
memcpy(extension_bytes, "NETSCAPE2.0", 11);
|
|
if (EGifPutExtensionFirst(gif_file, APPLICATION_EXT_FUNC_CODE, 11, extension_bytes) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frame_num);
|
|
|
|
extension_bytes[0] = 1;
|
|
extension_bytes[1] = (loop & 0xff);
|
|
extension_bytes[2] = (loop >> 8) & 0xff;
|
|
if (EGifPutExtensionNext(gif_file, APPLICATION_EXT_FUNC_CODE, 3, extension_bytes) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frame_num);
|
|
|
|
if (EGifPutExtensionLast(gif_file, APPLICATION_EXT_FUNC_CODE, 0, NULL) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frame_num);
|
|
}
|
|
|
|
// Write graphics extension record (to save the duration of the
|
|
// frame and maybe the transparency index).
|
|
{
|
|
unsigned char extension_bytes[5];
|
|
int disposal_method = (sprite->getBackgroundLayer() ? DISPOSAL_METHOD_DO_NOT_DISPOSE:
|
|
DISPOSAL_METHOD_RESTORE_BGCOLOR);
|
|
int frame_delay = sprite->getFrameDuration(frame_num) / 10;
|
|
|
|
extension_bytes[0] = (((disposal_method & 7) << 2) |
|
|
(transparent_index >= 0 ? 1: 0));
|
|
extension_bytes[1] = (frame_delay & 0xff);
|
|
extension_bytes[2] = (frame_delay >> 8) & 0xff;
|
|
extension_bytes[3] = (transparent_index >= 0 ? transparent_index: 0);
|
|
|
|
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", (int)frame_num);
|
|
}
|
|
|
|
// Image color map
|
|
ColorMapObject* image_color_map = NULL;
|
|
if (current_palette != previous_palette) {
|
|
image_color_map = MakeMapObject(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));
|
|
}
|
|
previous_palette = current_palette;
|
|
}
|
|
|
|
// Write the image record.
|
|
if (EGifPutImageDesc(gif_file,
|
|
frame_x, frame_y,
|
|
frame_w, frame_h, interlace ? 1: 0,
|
|
image_color_map) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF frame %d.\n", (int)frame_num);
|
|
|
|
// Write the image data (pixels).
|
|
if (interlace) {
|
|
// 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]) {
|
|
IndexedTraits::address_t addr = image_address_fast<IndexedTraits>(current_image, frame_x, frame_y + y);
|
|
if (EGifPutLine(gif_file, addr, frame_w) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF image scanlines for frame %d.\n", (int)frame_num);
|
|
}
|
|
}
|
|
else {
|
|
// Write all image scanlines (not interlaced in this case).
|
|
for (int y = 0; y < frame_h; ++y) {
|
|
IndexedTraits::address_t addr = image_address_fast<IndexedTraits>(current_image, frame_x, frame_y + y);
|
|
if (EGifPutLine(gif_file, addr, frame_w) == GIF_ERROR)
|
|
throw base::Exception("Error writing GIF image scanlines for frame %d.\n", (int)frame_num);
|
|
}
|
|
}
|
|
|
|
image_copy(previous_image, current_image, 0, 0);
|
|
}
|
|
|
|
return true;
|
|
}
|