2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
|
|
|
// Copyright (C) 2001-2015 David Capello
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License version 2 as
|
|
|
|
// published by the Free Software Foundation.
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2007-09-19 07:57:02 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2014-07-20 09:01:39 +08:00
|
|
|
#include "app/console.h"
|
|
|
|
#include "app/context.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/document.h"
|
|
|
|
#include "app/file/file.h"
|
|
|
|
#include "app/file/file_format.h"
|
|
|
|
#include "app/file/format_options.h"
|
2014-07-20 09:01:39 +08:00
|
|
|
#include "app/file/gif_options.h"
|
|
|
|
#include "app/ini_file.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/gui.h"
|
2014-02-18 08:43:20 +08:00
|
|
|
#include "app/util/autocrop.h"
|
|
|
|
#include "base/file_handle.h"
|
2015-07-17 23:26:11 +08:00
|
|
|
#include "base/fs.h"
|
2014-02-18 08:43:20 +08:00
|
|
|
#include "base/unique_ptr.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/doc.h"
|
2014-12-28 22:06:11 +08:00
|
|
|
#include "render/quantization.h"
|
|
|
|
#include "render/render.h"
|
2012-06-18 09:49:58 +08:00
|
|
|
#include "ui/alert.h"
|
2014-07-20 09:01:39 +08:00
|
|
|
#include "ui/button.h"
|
|
|
|
|
|
|
|
#include "generated_gif_options.h"
|
2011-01-19 07:42:43 +08:00
|
|
|
|
|
|
|
#include <gif_lib.h>
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <io.h>
|
|
|
|
#define posix_lseek _lseek
|
|
|
|
#else
|
|
|
|
#include <unistd.h>
|
|
|
|
#define posix_lseek _lseek
|
|
|
|
#endif
|
|
|
|
|
2015-06-09 07:21:50 +08:00
|
|
|
#if GIFLIB_MAJOR < 5
|
|
|
|
#define GifMakeMapObject MakeMapObject
|
|
|
|
#endif
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
2014-02-18 08:43:20 +08:00
|
|
|
using namespace base;
|
|
|
|
|
2015-07-16 04:13:37 +08:00
|
|
|
enum class DisposalMethod {
|
|
|
|
NONE,
|
|
|
|
DO_NOT_DISPOSE,
|
|
|
|
RESTORE_BGCOLOR,
|
|
|
|
RESTORE_PREVIOUS,
|
2011-01-19 07:42:43 +08:00
|
|
|
};
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
class GifFormat : public FileFormat {
|
2014-07-20 09:01:39 +08:00
|
|
|
|
2011-01-17 04:27:18 +08:00
|
|
|
const char* onGetName() const { return "gif"; }
|
|
|
|
const char* onGetExtensions() const { return "gif"; }
|
|
|
|
int onGetFlags() const {
|
2012-01-06 06:45:03 +08:00
|
|
|
return
|
2011-01-17 04:27:18 +08:00
|
|
|
FILE_SUPPORT_LOAD |
|
|
|
|
FILE_SUPPORT_SAVE |
|
2011-01-19 07:42:43 +08:00
|
|
|
FILE_SUPPORT_RGB |
|
|
|
|
FILE_SUPPORT_RGBA |
|
|
|
|
FILE_SUPPORT_GRAY |
|
|
|
|
FILE_SUPPORT_GRAYA |
|
2011-01-17 04:27:18 +08:00
|
|
|
FILE_SUPPORT_INDEXED |
|
|
|
|
FILE_SUPPORT_FRAMES |
|
2014-07-20 09:01:39 +08:00
|
|
|
FILE_SUPPORT_PALETTES |
|
|
|
|
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
2011-01-17 04:27:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool onLoad(FileOp* fop);
|
2014-04-10 11:33:28 +08:00
|
|
|
#ifdef ENABLE_SAVE
|
2014-08-15 10:07:47 +08:00
|
|
|
bool onSave(FileOp* fop) override;
|
2014-04-10 11:33:28 +08:00
|
|
|
#endif
|
2015-04-01 23:35:18 +08:00
|
|
|
base::SharedPtr<FormatOptions> onGetFormatOptions(FileOp* fop) override;
|
2007-09-19 07:57:02 +08:00
|
|
|
};
|
|
|
|
|
2011-01-17 04:27:18 +08:00
|
|
|
FileFormat* CreateGifFormat()
|
|
|
|
{
|
|
|
|
return new GifFormat;
|
|
|
|
}
|
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
static int interlaced_offset[] = { 0, 4, 2, 1 };
|
|
|
|
static int interlaced_jumps[] = { 8, 8, 4, 2 };
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2014-06-03 09:10:08 +08:00
|
|
|
struct GifFilePtr {
|
|
|
|
public:
|
2015-06-09 07:21:50 +08:00
|
|
|
#if GIFLIB_MAJOR >= 5
|
2014-06-03 09:10:08 +08:00
|
|
|
typedef int (*CloseFunc)(GifFileType*, int*);
|
2015-06-09 07:21:50 +08:00
|
|
|
#else
|
|
|
|
typedef int (*CloseFunc)(GifFileType*);
|
|
|
|
#endif
|
2014-06-03 09:10:08 +08:00
|
|
|
|
|
|
|
GifFilePtr(GifFileType* ptr, CloseFunc closeFunc) :
|
|
|
|
m_ptr(ptr), m_closeFunc(closeFunc) {
|
|
|
|
}
|
|
|
|
|
|
|
|
~GifFilePtr() {
|
2015-06-09 07:21:50 +08:00
|
|
|
#if GIFLIB_MAJOR >= 5
|
2014-06-03 09:10:08 +08:00
|
|
|
int errCode;
|
|
|
|
m_closeFunc(m_ptr, &errCode);
|
2015-06-09 07:21:50 +08:00
|
|
|
#else
|
|
|
|
m_closeFunc(m_ptr);
|
|
|
|
#endif
|
2014-06-03 09:10:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
operator GifFileType*() {
|
|
|
|
return m_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
GifFileType* operator->() {
|
|
|
|
return m_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
GifFileType* m_ptr;
|
|
|
|
CloseFunc m_closeFunc;
|
|
|
|
};
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Decodes a GIF file trying to keep the image in Indexed format. If
|
|
|
|
// it's not possible to handle it as Indexed (e.g. it contains more
|
|
|
|
// than 256 colors), the file will be automatically converted to RGB.
|
|
|
|
//
|
|
|
|
// This is a complex process because GIF files are made to be composed
|
|
|
|
// over RGB output. Each frame is composed over the previous frame,
|
|
|
|
// and combinations of local colormaps can output any number of
|
|
|
|
// colors, not just 256. So previous RGB colors must be kept and
|
|
|
|
// merged with new colormaps.
|
|
|
|
class GifDecoder {
|
|
|
|
public:
|
|
|
|
GifDecoder(FileOp* fop, GifFileType* gifFile, int fd, int filesize)
|
|
|
|
: m_fop(fop)
|
|
|
|
, m_gifFile(gifFile)
|
|
|
|
, m_fd(fd)
|
|
|
|
, m_filesize(filesize)
|
|
|
|
, m_sprite(nullptr)
|
|
|
|
, m_spriteBounds(0, 0, m_gifFile->SWidth, m_gifFile->SHeight)
|
|
|
|
, m_frameNum(0)
|
|
|
|
, m_opaque(false)
|
|
|
|
, m_disposalMethod(DisposalMethod::NONE)
|
|
|
|
, m_bgIndex(m_gifFile->SBackGroundColor >= 0 ? m_gifFile->SBackGroundColor: 0)
|
|
|
|
, m_localTransparentIndex(-1)
|
|
|
|
, m_frameDelay(1)
|
|
|
|
, m_remap(256) {
|
|
|
|
PRINTF("GIF background index = %d\n", (int)m_gifFile->SBackGroundColor);
|
|
|
|
}
|
2014-02-18 08:43:20 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
Sprite* releaseSprite() {
|
|
|
|
return m_sprite.release();
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
bool decode() {
|
|
|
|
GifRecordType recType;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Read record by record
|
|
|
|
while ((recType = readRecordType()) != TERMINATE_RECORD_TYPE) {
|
|
|
|
processRecord(recType);
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Just one frame?
|
|
|
|
if (m_fop->oneframe && m_frameNum > 0)
|
|
|
|
break;
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (fop_is_stop(m_fop))
|
|
|
|
break;
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (m_filesize > 0) {
|
|
|
|
int pos = posix_lseek(m_fd, 0, SEEK_CUR);
|
|
|
|
fop_progress(m_fop, double(pos) / double(m_filesize));
|
|
|
|
}
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
|
|
|
|
if (m_layer && m_opaque)
|
|
|
|
m_layer->configureAsBackground();
|
|
|
|
|
|
|
|
return (m_sprite != nullptr);
|
2011-03-23 08:11:25 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
private:
|
|
|
|
|
|
|
|
GifRecordType readRecordType() {
|
|
|
|
GifRecordType type;
|
|
|
|
if (DGifGetRecordType(m_gifFile, &type) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Invalid GIF record in file.\n");
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
return type;
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void processRecord(GifRecordType recordType) {
|
|
|
|
switch (recordType) {
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
case IMAGE_DESC_RECORD_TYPE:
|
|
|
|
processImageDescRecord();
|
|
|
|
break;
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
case EXTENSION_RECORD_TYPE:
|
|
|
|
processExtensionRecord();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void processImageDescRecord() {
|
|
|
|
if (DGifGetImageDesc(m_gifFile) == GIF_ERROR)
|
|
|
|
throw Exception("Invalid GIF image descriptor.\n");
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// These are the bounds of the image to read.
|
|
|
|
gfx::Rect frameBounds(
|
|
|
|
m_gifFile->Image.Left,
|
|
|
|
m_gifFile->Image.Top,
|
|
|
|
m_gifFile->Image.Width,
|
|
|
|
m_gifFile->Image.Height);
|
2015-07-16 02:42:54 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (!m_spriteBounds.contains(frameBounds))
|
|
|
|
throw Exception("Image %d is out of sprite bounds.\n", (int)m_frameNum);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Create sprite if this is the first frame
|
|
|
|
if (!m_sprite)
|
|
|
|
createSprite();
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Add a frame if it's necessary
|
|
|
|
if (m_sprite->lastFrame() < m_frameNum)
|
|
|
|
m_sprite->addFrame(m_frameNum);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Create a temporary image loading the frame pixels from the GIF file
|
|
|
|
UniquePtr<Image> frameImage(
|
|
|
|
readFrameIndexedImage(frameBounds));
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
PRINTF("Frame[%d] transparent index = %d\n", (int)m_frameNum, m_localTransparentIndex);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (m_frameNum == 0) {
|
|
|
|
if (m_localTransparentIndex >= 0)
|
|
|
|
m_opaque = false;
|
|
|
|
else
|
|
|
|
m_opaque = true;
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Merge this frame colors with the current palette
|
|
|
|
updatePalette(frameImage);
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Convert the sprite to RGB if we have more than 256 colors
|
|
|
|
if ((m_sprite->pixelFormat() == IMAGE_INDEXED) &&
|
|
|
|
(m_sprite->palette(m_frameNum)->size() > 256)) {
|
|
|
|
convertIndexedSpriteToRgb();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Composite frame with previous frame
|
|
|
|
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
|
|
|
compositeIndexedImageToIndexed(frameBounds, frameImage);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
compositeIndexedImageToRgb(frameBounds, frameImage);
|
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Create cel
|
|
|
|
createCel();
|
|
|
|
|
|
|
|
// Dispose/clear frame content
|
|
|
|
processDisposalMethod(frameBounds);
|
|
|
|
|
|
|
|
// Set frame delay (1/100th seconds to milliseconds)
|
|
|
|
if (m_frameDelay >= 0)
|
|
|
|
m_sprite->setFrameDuration(m_frameNum, m_frameDelay*10);
|
|
|
|
|
|
|
|
// Reset extension variables
|
|
|
|
m_disposalMethod = DisposalMethod::NONE;
|
|
|
|
m_localTransparentIndex = -1;
|
|
|
|
m_frameDelay = 1;
|
|
|
|
|
|
|
|
// Next frame
|
|
|
|
++m_frameNum;
|
|
|
|
}
|
|
|
|
|
|
|
|
Image* readFrameIndexedImage(const gfx::Rect& frameBounds) {
|
|
|
|
UniquePtr<Image> frameImage(
|
|
|
|
Image::create(IMAGE_INDEXED, frameBounds.w, frameBounds.h));
|
|
|
|
|
|
|
|
IndexedTraits::address_t addr;
|
|
|
|
|
|
|
|
if (m_gifFile->Image.Interlace) {
|
|
|
|
// Need to perform 4 passes on the image
|
|
|
|
for (int i=0; i<4; ++i)
|
|
|
|
for (int y = interlaced_offset[i]; y < frameBounds.h; y += interlaced_jumps[i]) {
|
|
|
|
addr = frameImage->getPixelAddress(0, y);
|
|
|
|
if (DGifGetLine(m_gifFile, addr, frameBounds.w) == GIF_ERROR)
|
|
|
|
throw Exception("Invalid interlaced image data.");
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
for (int y = 0; y < frameBounds.h; ++y) {
|
|
|
|
addr = frameImage->getPixelAddress(0, y);
|
|
|
|
if (DGifGetLine(m_gifFile, addr, frameBounds.w) == GIF_ERROR)
|
|
|
|
throw Exception("Invalid image data (%d).\n"
|
|
|
|
#if GIFLIB_MAJOR >= 5
|
|
|
|
, m_gifFile->Error
|
|
|
|
#else
|
|
|
|
, GifLastError()
|
|
|
|
#endif
|
|
|
|
);
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
}
|
2011-01-24 06:35:22 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
return frameImage.release();
|
|
|
|
}
|
2011-01-24 06:35:22 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
ColorMapObject* getFrameColormap() {
|
|
|
|
ColorMapObject* colormap = m_gifFile->Image.ColorMap;
|
|
|
|
if (!colormap)
|
|
|
|
colormap = m_gifFile->SColorMap;
|
|
|
|
if (!colormap)
|
|
|
|
throw Exception("There is no color map.");
|
|
|
|
return colormap;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adds colors used in the GIF frame so we can draw it over
|
|
|
|
// m_currentImage. If the frame contains a local colormap, we try to
|
|
|
|
// find them in the current sprite palette (using
|
|
|
|
// Palette::findExactMatch()) so we don't add duplicated entries.
|
|
|
|
// To do so we use a Remap (m_remap variable) which matches the
|
|
|
|
// original GIF frame colors with the current sprite colors.
|
|
|
|
void updatePalette(const Image* frameImage) {
|
|
|
|
ColorMapObject* colormap = getFrameColormap();
|
|
|
|
int ncolors = colormap->ColorCount;
|
|
|
|
bool isLocalColormap = (m_gifFile->Image.ColorMap ? true: false);
|
|
|
|
|
|
|
|
// Get the list of used palette entries
|
|
|
|
PalettePicks usedEntries(ncolors);
|
|
|
|
if (isLocalColormap) {
|
|
|
|
for (const auto& i : LockImageBits<IndexedTraits>(frameImage)) {
|
|
|
|
if (i >= 0 && i < ncolors)
|
|
|
|
usedEntries[i] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Used all entries if the colormap is global
|
|
|
|
else
|
|
|
|
usedEntries.all();
|
|
|
|
|
|
|
|
int usedncolors = usedEntries.picks();
|
|
|
|
|
|
|
|
// Check if we need an extra color equal to the bg color in a
|
|
|
|
// transparent frameImage.
|
|
|
|
bool needsExtraBgColor = false;
|
|
|
|
if (!m_opaque && m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
|
|
|
for (const auto& i : LockImageBits<IndexedTraits>(frameImage)) {
|
|
|
|
if (i == m_bgIndex &&
|
|
|
|
i != m_localTransparentIndex) {
|
|
|
|
needsExtraBgColor = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-03-23 08:11:25 +08:00
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (m_frameNum > 0 && !isLocalColormap)
|
|
|
|
return;
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
UniquePtr<Palette> palette;
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (m_frameNum == 0)
|
|
|
|
palette.reset(new Palette(m_frameNum, usedncolors + (needsExtraBgColor ? 1: 0)));
|
|
|
|
else {
|
|
|
|
palette.reset(new Palette(*m_sprite->palette(m_frameNum-1)));
|
|
|
|
palette->setFrame(m_frameNum);
|
|
|
|
}
|
|
|
|
resetRemap(MAX(ncolors, palette->size()));
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
int found = 0;
|
|
|
|
if (m_frameNum > 0) {
|
|
|
|
for (int i=0; i<ncolors; ++i) {
|
|
|
|
if (!usedEntries[i])
|
|
|
|
continue;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
int j = palette->findExactMatch(
|
|
|
|
colormap->Colors[i].Red,
|
|
|
|
colormap->Colors[i].Green,
|
|
|
|
colormap->Colors[i].Blue, 255,
|
|
|
|
(m_opaque ? -1: m_bgIndex));
|
|
|
|
if (j >= 0) {
|
|
|
|
m_remap.map(i, j);
|
|
|
|
++found;
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (found == usedncolors)
|
|
|
|
return;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
Palette oldPalette(*palette);
|
|
|
|
int base = (m_frameNum == 0 ? 0: palette->size());
|
|
|
|
int missing = usedncolors - found;
|
|
|
|
palette->resize(base + missing + (needsExtraBgColor ? 1: 0));
|
|
|
|
resetRemap(MAX(ncolors, palette->size()));
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
for (int i=0; i<ncolors; ++i) {
|
|
|
|
if (!usedEntries[i])
|
|
|
|
continue;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
int j = -1;
|
|
|
|
if (m_frameNum > 0) {
|
|
|
|
j = oldPalette.findExactMatch(
|
|
|
|
colormap->Colors[i].Red,
|
|
|
|
colormap->Colors[i].Green,
|
|
|
|
colormap->Colors[i].Blue, 255,
|
|
|
|
(m_opaque ? -1: m_bgIndex));
|
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (j < 0) {
|
|
|
|
j = base++;
|
|
|
|
palette->setEntry(
|
|
|
|
j, rgba(
|
|
|
|
colormap->Colors[i].Red,
|
|
|
|
colormap->Colors[i].Green,
|
|
|
|
colormap->Colors[i].Blue, 255));
|
|
|
|
}
|
|
|
|
m_remap.map(i, j);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needsExtraBgColor) {
|
|
|
|
int j = base++;
|
|
|
|
palette->setEntry(j, palette->getEntry(m_bgIndex));
|
|
|
|
m_remap.map(m_bgIndex, j);
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(base == palette->size());
|
|
|
|
m_sprite->setPalette(palette, false);
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void compositeIndexedImageToIndexed(const gfx::Rect& frameBounds,
|
|
|
|
const Image* frameImage) {
|
|
|
|
// Compose the frame image with the previous frame
|
|
|
|
for (int y=0; y<frameBounds.h; ++y) {
|
|
|
|
for (int x=0; x<frameBounds.w; ++x) {
|
|
|
|
color_t i = get_pixel_fast<IndexedTraits>(frameImage, x, y);
|
|
|
|
if (i == m_localTransparentIndex)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
i = m_remap[i];
|
|
|
|
put_pixel_fast<IndexedTraits>(m_currentImage.get(),
|
|
|
|
frameBounds.x + x,
|
|
|
|
frameBounds.y + y, i);
|
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void compositeIndexedImageToRgb(const gfx::Rect& frameBounds,
|
|
|
|
const Image* frameImage) {
|
|
|
|
ColorMapObject* colormap = getFrameColormap();
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
// Compose the frame image with the previous frame
|
|
|
|
for (int y=0; y<frameBounds.h; ++y) {
|
|
|
|
for (int x=0; x<frameBounds.w; ++x) {
|
|
|
|
color_t i = get_pixel_fast<IndexedTraits>(frameImage, x, y);
|
|
|
|
if (i == m_localTransparentIndex)
|
|
|
|
continue;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
i = rgba(
|
|
|
|
colormap->Colors[i].Red,
|
|
|
|
colormap->Colors[i].Green,
|
|
|
|
colormap->Colors[i].Blue, 255);
|
|
|
|
|
|
|
|
put_pixel_fast<RgbTraits>(m_currentImage.get(),
|
|
|
|
frameBounds.x + x,
|
|
|
|
frameBounds.y + y, i);
|
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void createCel() {
|
|
|
|
Cel* cel = new Cel(m_frameNum, ImageRef(0));
|
2011-06-26 04:12:08 +08:00
|
|
|
try {
|
2015-07-17 23:26:11 +08:00
|
|
|
ImageRef celImage(Image::createCopy(m_currentImage.get()));
|
2011-06-26 04:12:08 +08:00
|
|
|
try {
|
2015-07-17 23:26:11 +08:00
|
|
|
cel->data()->setImage(celImage);
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
catch (...) {
|
2012-01-06 06:45:03 +08:00
|
|
|
throw;
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
m_layer->addCel(cel);
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
catch (...) {
|
|
|
|
delete cel;
|
|
|
|
throw;
|
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void processDisposalMethod(const gfx::Rect& frameBounds) {
|
|
|
|
// The m_currentImage was already copied to represent the current
|
|
|
|
// frame (m_frameNum), so now we have to clear the area occupied
|
|
|
|
// by frameImage using the desired disposal method.
|
|
|
|
switch (m_disposalMethod) {
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-16 04:13:37 +08:00
|
|
|
case DisposalMethod::NONE:
|
|
|
|
case DisposalMethod::DO_NOT_DISPOSE:
|
2012-01-06 06:45:03 +08:00
|
|
|
// Do nothing
|
|
|
|
break;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-16 04:13:37 +08:00
|
|
|
case DisposalMethod::RESTORE_BGCOLOR:
|
2015-07-17 23:26:11 +08:00
|
|
|
fill_rect(m_currentImage.get(),
|
|
|
|
frameBounds.x,
|
|
|
|
frameBounds.y,
|
|
|
|
frameBounds.x+frameBounds.w-1,
|
|
|
|
frameBounds.y+frameBounds.h-1,
|
|
|
|
m_bgIndex);
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
2011-06-26 04:12:08 +08:00
|
|
|
|
2015-07-16 04:13:37 +08:00
|
|
|
case DisposalMethod::RESTORE_PREVIOUS:
|
2015-07-17 23:26:11 +08:00
|
|
|
copy_image(m_currentImage.get(), m_previousImage.get());
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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).
|
2015-07-17 23:26:11 +08:00
|
|
|
if (m_disposalMethod != DisposalMethod::RESTORE_PREVIOUS)
|
|
|
|
copy_image(m_previousImage.get(), m_currentImage.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
void processExtensionRecord() {
|
|
|
|
int extCode;
|
|
|
|
GifByteType* extension;
|
|
|
|
if (DGifGetExtension(m_gifFile, &extCode, &extension) == GIF_ERROR)
|
|
|
|
throw Exception("Invalid GIF extension record.\n");
|
2015-07-16 02:09:07 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
if (extCode == GRAPHICS_EXT_FUNC_CODE) {
|
|
|
|
if (extension[0] >= 4) {
|
|
|
|
m_disposalMethod = (DisposalMethod)((extension[1] >> 2) & 7);
|
|
|
|
m_localTransparentIndex = (extension[1] & 1) ? extension[4]: -1;
|
|
|
|
m_frameDelay = (extension[3] << 8) | extension[2];
|
|
|
|
|
|
|
|
// PRINTF("Disposal method: %d\nTransparent index: %d\nFrame delay: %d\n",
|
|
|
|
// disposal_method, transparentIndex, frame_delay);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
while (extension) {
|
|
|
|
if (DGifGetExtensionNext(m_gifFile, &extension) == GIF_ERROR)
|
|
|
|
throw Exception("Invalid GIF extension record.\n");
|
|
|
|
}
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
void createSprite() {
|
|
|
|
ColorMapObject* colormap = nullptr;
|
|
|
|
if (m_gifFile->SColorMap) {
|
|
|
|
colormap = m_gifFile->SColorMap;
|
|
|
|
}
|
|
|
|
else if (m_gifFile->Image.ColorMap) {
|
|
|
|
colormap = m_gifFile->Image.ColorMap;
|
|
|
|
}
|
|
|
|
int ncolors = (colormap ? colormap->ColorCount: 1);
|
|
|
|
int w = m_spriteBounds.w;
|
|
|
|
int h = m_spriteBounds.h;;
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
m_sprite.reset(new Sprite(IMAGE_INDEXED, w, h, ncolors));
|
|
|
|
m_sprite->setTransparentColor(m_bgIndex);
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
m_currentImage.reset(Image::create(IMAGE_INDEXED, w, h));
|
|
|
|
m_previousImage.reset(Image::create(IMAGE_INDEXED, w, h));
|
|
|
|
clear_image(m_currentImage.get(), m_bgIndex);
|
|
|
|
clear_image(m_previousImage.get(), m_bgIndex);
|
|
|
|
|
|
|
|
m_layer = new LayerImage(m_sprite.get());
|
|
|
|
m_sprite->folder()->addLayer(m_layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void resetRemap(int ncolors) {
|
|
|
|
m_remap = Remap(ncolors);
|
|
|
|
for (int i=0; i<ncolors; ++i)
|
|
|
|
m_remap.map(i, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Converts the whole sprite read so far because it contains more
|
|
|
|
// than 256 colors at the same time.
|
|
|
|
void convertIndexedSpriteToRgb() {
|
|
|
|
for (Cel* cel : m_sprite->uniqueCels()) {
|
|
|
|
Image* oldImage = cel->image();
|
|
|
|
|
|
|
|
ImageRef newImage(
|
|
|
|
render::convert_pixel_format
|
|
|
|
(oldImage, NULL, IMAGE_RGB, DitheringMethod::NONE,
|
|
|
|
m_sprite->rgbMap(cel->frame()),
|
|
|
|
m_sprite->palette(cel->frame()),
|
|
|
|
m_opaque, // is background
|
|
|
|
m_bgIndex));
|
|
|
|
|
|
|
|
m_sprite->replaceImage(oldImage->id(), newImage);
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
|
2015-07-17 23:26:11 +08:00
|
|
|
m_currentImage.reset(
|
|
|
|
render::convert_pixel_format
|
|
|
|
(m_currentImage.get(), NULL, IMAGE_RGB, DitheringMethod::NONE,
|
|
|
|
m_sprite->rgbMap(m_frameNum),
|
|
|
|
m_sprite->palette(m_frameNum),
|
|
|
|
m_opaque,
|
|
|
|
m_bgIndex));
|
|
|
|
|
|
|
|
m_previousImage.reset(
|
|
|
|
render::convert_pixel_format
|
|
|
|
(m_previousImage.get(), NULL, IMAGE_RGB, DitheringMethod::NONE,
|
|
|
|
m_sprite->rgbMap(MAX(0, m_frameNum-1)),
|
|
|
|
m_sprite->palette(MAX(0, m_frameNum-1)),
|
|
|
|
m_opaque,
|
|
|
|
m_bgIndex));
|
|
|
|
|
|
|
|
m_sprite->setPixelFormat(IMAGE_RGB);
|
|
|
|
}
|
|
|
|
|
|
|
|
FileOp* m_fop;
|
|
|
|
GifFileType* m_gifFile;
|
|
|
|
int m_fd;
|
|
|
|
int m_filesize;
|
|
|
|
UniquePtr<Sprite> m_sprite;
|
|
|
|
gfx::Rect m_spriteBounds;
|
|
|
|
LayerImage* m_layer;
|
|
|
|
int m_frameNum;
|
|
|
|
bool m_opaque;
|
|
|
|
DisposalMethod m_disposalMethod;
|
|
|
|
int m_bgIndex;
|
|
|
|
int m_localTransparentIndex;
|
|
|
|
int m_frameDelay;
|
|
|
|
ImageRef m_currentImage;
|
|
|
|
ImageRef m_previousImage;
|
|
|
|
Remap m_remap;
|
|
|
|
};
|
|
|
|
|
|
|
|
bool GifFormat::onLoad(FileOp* fop)
|
|
|
|
{
|
|
|
|
// The filesize is used only to report some progress when we decode
|
|
|
|
// the GIF file.
|
|
|
|
int filesize = base::file_size(fop->filename);
|
|
|
|
|
|
|
|
#if GIFLIB_MAJOR >= 5
|
|
|
|
int errCode = 0;
|
|
|
|
#endif
|
|
|
|
int fd = open_file_descriptor_with_exception(fop->filename, "rb");
|
|
|
|
GifFilePtr gif_file(DGifOpenFileHandle(fd
|
|
|
|
#if GIFLIB_MAJOR >= 5
|
|
|
|
, &errCode
|
|
|
|
#endif
|
|
|
|
), &DGifCloseFile);
|
|
|
|
|
|
|
|
if (!gif_file) {
|
|
|
|
fop_error(fop, "Error loading GIF header.\n");
|
|
|
|
return false;
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
2015-07-17 23:26:11 +08:00
|
|
|
|
|
|
|
GifDecoder decoder(fop, gif_file, fd, filesize);
|
|
|
|
if (decoder.decode()) {
|
|
|
|
fop->createDocument(decoder.releaseSprite());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
2011-06-26 04:12:08 +08:00
|
|
|
}
|
|
|
|
|
2014-04-10 11:33:28 +08:00
|
|
|
#ifdef ENABLE_SAVE
|
2015-07-17 23:45:59 +08:00
|
|
|
static int next_power_of_two(int color_map_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);
|
|
|
|
return color_map_size;
|
|
|
|
}
|
|
|
|
|
2011-01-17 04:27:18 +08:00
|
|
|
bool GifFormat::onSave(FileOp* fop)
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2015-06-09 21:37:19 +08:00
|
|
|
#if GIFLIB_MAJOR >= 5
|
2015-06-09 07:21:50 +08:00
|
|
|
int errCode = 0;
|
2015-06-09 21:37:19 +08:00
|
|
|
#endif
|
2015-06-09 07:21:50 +08:00
|
|
|
GifFilePtr gif_file(EGifOpenFileHandle(open_file_descriptor_with_exception(fop->filename, "wb")
|
|
|
|
#if GIFLIB_MAJOR >= 5
|
|
|
|
, &errCode
|
|
|
|
#endif
|
|
|
|
), &EGifCloseFile);
|
2014-02-18 08:43:20 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
if (!gif_file)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error creating GIF file.\n");
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-04-01 23:35:18 +08:00
|
|
|
base::SharedPtr<GifOptions> gif_options = fop->seq.format_options;
|
2014-07-29 11:53:24 +08:00
|
|
|
Sprite* sprite = fop->document->sprite();
|
2014-07-30 12:28:15 +08:00
|
|
|
int sprite_w = sprite->width();
|
|
|
|
int sprite_h = sprite->height();
|
|
|
|
PixelFormat sprite_format = sprite->pixelFormat();
|
2014-07-20 09:01:39 +08:00
|
|
|
bool interlaced = gif_options->interlaced();
|
2015-01-29 22:24:43 +08:00
|
|
|
int loop = (gif_options->loop() ? 0: -1);
|
2015-04-21 21:01:28 +08:00
|
|
|
bool has_background = (sprite->backgroundLayer() && sprite->backgroundLayer()->isVisible());
|
2014-07-30 12:28:15 +08:00
|
|
|
int background_color = (sprite_format == IMAGE_INDEXED ? sprite->transparentColor(): 0);
|
|
|
|
int transparent_index = (has_background ? -1: sprite->transparentColor());
|
2014-07-20 09:01:39 +08:00
|
|
|
|
2014-12-29 07:39:11 +08:00
|
|
|
Palette current_palette = *sprite->palette(frame_t(0));
|
2014-07-20 09:01:39 +08:00
|
|
|
Palette previous_palette(current_palette);
|
|
|
|
RgbMap rgbmap;
|
|
|
|
|
|
|
|
// The color map must be a power of two.
|
2015-07-17 23:45:59 +08:00
|
|
|
int color_map_size = next_power_of_two(current_palette.size());
|
2014-07-20 09:01:39 +08:00
|
|
|
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();
|
|
|
|
|
2015-07-17 23:45:59 +08:00
|
|
|
for (int i = 0; i < color_map_size; ++i) {
|
|
|
|
color_t color;
|
|
|
|
if (i < current_palette.size())
|
|
|
|
color = current_palette.getEntry(i);
|
|
|
|
else
|
|
|
|
color = rgba(0, 0, 0, 255);
|
|
|
|
|
|
|
|
color_map->Colors[i].Red = rgba_getr(color);
|
|
|
|
color_map->Colors[i].Green = rgba_getg(color);
|
|
|
|
color_map->Colors[i].Blue = rgba_getb(color);
|
2014-07-20 09:01:39 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2014-07-20 09:01:39 +08:00
|
|
|
bpp = color_map->BitsPerPixel;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bpp = 8;
|
|
|
|
|
|
|
|
if (EGifPutScreenDesc(gif_file, sprite_w, sprite_h, bpp,
|
2012-01-06 06:45:03 +08:00
|
|
|
background_color, color_map) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error writing GIF header.\n");
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2014-02-18 08:43:20 +08:00
|
|
|
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));
|
2011-01-19 07:42:43 +08:00
|
|
|
int frame_x, frame_y, frame_w, frame_h;
|
|
|
|
int u1, v1, u2, v2;
|
|
|
|
int i1, j1, i2, j2;
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
// If the sprite is not Indexed type, we will need a temporary
|
|
|
|
// buffer to render the full RGB or Grayscale sprite.
|
2012-02-13 10:21:06 +08:00
|
|
|
if (sprite_format != IMAGE_INDEXED)
|
|
|
|
buffer_image.reset(Image::create(sprite_format, sprite_w, sprite_h));
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2013-11-10 06:59:05 +08:00
|
|
|
clear_image(current_image, background_color);
|
|
|
|
clear_image(previous_image, background_color);
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2014-07-20 09:01:39 +08:00
|
|
|
ColorMapObject* image_color_map = NULL;
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2014-12-28 22:06:11 +08:00
|
|
|
render::Render render;
|
|
|
|
render.setBgType(render::BgType::NONE);
|
|
|
|
|
2014-08-09 23:51:11 +08:00
|
|
|
// Check if the user wants one optimized palette for all frames.
|
|
|
|
if (sprite_format != IMAGE_INDEXED &&
|
|
|
|
gif_options->quantize() == GifOptions::QuantizeAll) {
|
|
|
|
// Feed the optimizer with all rendered frames.
|
2014-12-28 22:06:11 +08:00
|
|
|
render::PaletteOptimizer optimizer;
|
2014-12-29 07:39:11 +08:00
|
|
|
for (frame_t frame_num(0); frame_num<sprite->totalFrames(); ++frame_num) {
|
2014-08-09 23:51:11 +08:00
|
|
|
clear_image(buffer_image, background_color);
|
2014-12-28 22:06:11 +08:00
|
|
|
render.renderSprite(buffer_image, sprite, frame_num);
|
2015-07-02 22:18:43 +08:00
|
|
|
optimizer.feedWithImage(buffer_image, false);
|
2014-08-09 23:51:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
current_palette.makeBlack();
|
|
|
|
optimizer.calculate(¤t_palette, has_background);
|
|
|
|
|
|
|
|
rgbmap.regenerate(¤t_palette, transparent_index);
|
|
|
|
}
|
|
|
|
|
2014-12-29 07:39:11 +08:00
|
|
|
for (frame_t frame_num(0); frame_num<sprite->totalFrames(); ++frame_num) {
|
2011-01-19 07:42:43 +08:00
|
|
|
// If the sprite is RGB or Grayscale, we must to convert it to Indexed on the fly.
|
2012-02-13 10:21:06 +08:00
|
|
|
if (sprite_format != IMAGE_INDEXED) {
|
2014-07-20 09:01:39 +08:00
|
|
|
clear_image(buffer_image, background_color);
|
2014-12-28 22:06:11 +08:00
|
|
|
render.renderSprite(buffer_image, sprite, frame_num);
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2014-07-20 09:01:39 +08:00
|
|
|
switch (gif_options->quantize()) {
|
|
|
|
case GifOptions::NoQuantize:
|
2014-12-29 07:39:11 +08:00
|
|
|
sprite->palette(frame_num)->copyColorsTo(¤t_palette);
|
2014-08-09 23:51:11 +08:00
|
|
|
rgbmap.regenerate(¤t_palette, transparent_index);
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
2014-07-20 09:01:39 +08:00
|
|
|
case GifOptions::QuantizeEach:
|
|
|
|
{
|
|
|
|
current_palette.makeBlack();
|
|
|
|
|
|
|
|
std::vector<Image*> imgarray(1);
|
|
|
|
imgarray[0] = buffer_image;
|
2015-07-02 22:18:43 +08:00
|
|
|
render::create_palette_from_images(imgarray, ¤t_palette, has_background, false);
|
2014-08-09 23:51:11 +08:00
|
|
|
rgbmap.regenerate(¤t_palette, transparent_index);
|
2014-07-20 09:01:39 +08:00
|
|
|
}
|
2012-01-06 06:45:03 +08:00
|
|
|
break;
|
2014-08-09 23:51:11 +08:00
|
|
|
case GifOptions::QuantizeAll:
|
|
|
|
// Do nothing, we've already calculate the palette for all frames.
|
|
|
|
break;
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
2014-07-20 09:01:39 +08:00
|
|
|
|
2014-12-28 22:06:11 +08:00
|
|
|
render::convert_pixel_format(
|
2014-07-20 09:01:39 +08:00
|
|
|
buffer_image,
|
|
|
|
current_image,
|
|
|
|
IMAGE_INDEXED,
|
|
|
|
gif_options->dithering(),
|
|
|
|
&rgbmap,
|
|
|
|
¤t_palette,
|
2015-07-14 20:44:31 +08:00
|
|
|
has_background,
|
|
|
|
current_image->maskColor());
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
|
|
|
// If the sprite is Indexed, we can render directly into "current_image".
|
|
|
|
else {
|
2013-11-10 06:59:05 +08:00
|
|
|
clear_image(current_image, background_color);
|
2014-12-28 22:06:11 +08:00
|
|
|
render.renderSprite(current_image, sprite, frame_num);
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
if (frame_num == 0) {
|
|
|
|
frame_x = 0;
|
|
|
|
frame_y = 0;
|
2014-07-30 12:28:15 +08:00
|
|
|
frame_w = sprite->width();
|
|
|
|
frame_h = sprite->height();
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
else {
|
2011-01-19 07:42:43 +08:00
|
|
|
// Get the rectangle where start differences with the previous frame.
|
|
|
|
if (get_shrink_rect2(&u1, &v1, &u2, &v2, current_image, previous_image)) {
|
2012-01-06 06:45:03 +08:00
|
|
|
// 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;
|
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
// Specify loop extension.
|
|
|
|
if (frame_num == 0 && loop >= 0) {
|
2015-06-09 07:21:50 +08:00
|
|
|
#if GIFLIB_MAJOR >= 5
|
2014-06-03 09:10:08 +08:00
|
|
|
if (EGifPutExtensionLeader(gif_file, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR)
|
2014-06-04 11:27:34 +08:00
|
|
|
throw Exception("Error writing GIF graphics extension record (header section).");
|
2014-06-03 09:10:08 +08:00
|
|
|
|
2014-06-04 11:27:34 +08:00
|
|
|
unsigned char extension_bytes[11];
|
2011-01-19 07:42:43 +08:00
|
|
|
memcpy(extension_bytes, "NETSCAPE2.0", 11);
|
2014-06-03 09:10:08 +08:00
|
|
|
if (EGifPutExtensionBlock(gif_file, 11, extension_bytes) == GIF_ERROR)
|
2014-06-04 11:27:34 +08:00
|
|
|
throw Exception("Error writing GIF graphics extension record (first block).");
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
extension_bytes[0] = 1;
|
|
|
|
extension_bytes[1] = (loop & 0xff);
|
|
|
|
extension_bytes[2] = (loop >> 8) & 0xff;
|
2014-06-03 09:10:08 +08:00
|
|
|
if (EGifPutExtensionBlock(gif_file, 3, extension_bytes) == GIF_ERROR)
|
2014-06-04 11:27:34 +08:00
|
|
|
throw Exception("Error writing GIF graphics extension record (second block).");
|
|
|
|
|
|
|
|
if (EGifPutExtensionTrailer(gif_file) == GIF_ERROR)
|
|
|
|
throw Exception("Error writing GIF graphics extension record (trailer section).");
|
2015-06-09 07:21:50 +08:00
|
|
|
|
|
|
|
#else
|
|
|
|
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 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 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 Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frame_num);
|
|
|
|
#endif
|
2014-06-04 11:27:34 +08:00
|
|
|
}
|
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
// Write graphics extension record (to save the duration of the
|
|
|
|
// frame and maybe the transparency index).
|
|
|
|
{
|
|
|
|
unsigned char extension_bytes[5];
|
2015-07-16 04:13:37 +08:00
|
|
|
DisposalMethod disposal_method =
|
|
|
|
(sprite->backgroundLayer() ? DisposalMethod::DO_NOT_DISPOSE:
|
|
|
|
DisposalMethod::RESTORE_BGCOLOR);
|
2014-12-29 07:39:11 +08:00
|
|
|
int frame_delay = sprite->frameDuration(frame_num) / 10;
|
2011-01-19 07:42:43 +08:00
|
|
|
|
2015-07-16 04:13:37 +08:00
|
|
|
extension_bytes[0] = (((int(disposal_method) & 7) << 2) |
|
2012-01-06 06:45:03 +08:00
|
|
|
(transparent_index >= 0 ? 1: 0));
|
2011-01-19 07:42:43 +08:00
|
|
|
extension_bytes[1] = (frame_delay & 0xff);
|
|
|
|
extension_bytes[2] = (frame_delay >> 8) & 0xff;
|
2011-06-26 04:12:08 +08:00
|
|
|
extension_bytes[3] = (transparent_index >= 0 ? transparent_index: 0);
|
2011-01-19 07:42:43 +08:00
|
|
|
|
|
|
|
if (EGifPutExtension(gif_file, GRAPHICS_EXT_FUNC_CODE, 4, extension_bytes) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error writing GIF graphics extension record for frame %d.\n", (int)frame_num);
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
// Image color map
|
2014-07-20 09:01:39 +08:00
|
|
|
if ((!color_map && frame_num == 0) ||
|
|
|
|
(current_palette.countDiff(&previous_palette, NULL, NULL) > 0)) {
|
|
|
|
if (!image_color_map) {
|
2015-07-17 23:45:59 +08:00
|
|
|
color_map_size = next_power_of_two(current_palette.size());
|
|
|
|
image_color_map = GifMakeMapObject(color_map_size, NULL);
|
2014-07-20 09:01:39 +08:00
|
|
|
if (image_color_map == NULL)
|
|
|
|
throw std::bad_alloc();
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
2014-07-20 09:01:39 +08:00
|
|
|
|
2015-07-17 23:45:59 +08:00
|
|
|
for (int i = 0; i < color_map_size; ++i) {
|
|
|
|
color_t color;
|
|
|
|
if (i < current_palette.size())
|
|
|
|
color = current_palette.getEntry(i);
|
|
|
|
else
|
|
|
|
color = rgba(0, 0, 0, 255);
|
|
|
|
|
|
|
|
image_color_map->Colors[i].Red = rgba_getr(color);
|
|
|
|
image_color_map->Colors[i].Green = rgba_getg(color);
|
|
|
|
image_color_map->Colors[i].Blue = rgba_getb(color);
|
2014-07-20 09:01:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
current_palette.copyColorsTo(&previous_palette);
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write the image record.
|
|
|
|
if (EGifPutImageDesc(gif_file,
|
2012-01-06 06:45:03 +08:00
|
|
|
frame_x, frame_y,
|
2014-07-20 09:01:39 +08:00
|
|
|
frame_w, frame_h, interlaced ? 1: 0,
|
2012-01-06 06:45:03 +08:00
|
|
|
image_color_map) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error writing GIF frame %d.\n", (int)frame_num);
|
2011-01-19 07:42:43 +08:00
|
|
|
|
|
|
|
// Write the image data (pixels).
|
2014-07-20 09:01:39 +08:00
|
|
|
if (interlaced) {
|
2011-01-19 07:42:43 +08:00
|
|
|
// Need to perform 4 passes on the images.
|
|
|
|
for (int i=0; i<4; ++i)
|
2012-01-06 06:45:03 +08:00
|
|
|
for (int y = interlaced_offset[i]; y < frame_h; y += interlaced_jumps[i]) {
|
2013-11-10 06:59:05 +08:00
|
|
|
IndexedTraits::address_t addr =
|
|
|
|
(IndexedTraits::address_t)current_image->getPixelAddress(frame_x, frame_y + y);
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
if (EGifPutLine(gif_file, addr, frame_w) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error writing GIF image scanlines for frame %d.\n", (int)frame_num);
|
2012-01-06 06:45:03 +08:00
|
|
|
}
|
2011-01-19 07:42:43 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Write all image scanlines (not interlaced in this case).
|
2014-07-20 09:01:39 +08:00
|
|
|
for (int y=0; y<frame_h; ++y) {
|
2013-11-10 06:59:05 +08:00
|
|
|
IndexedTraits::address_t addr =
|
|
|
|
(IndexedTraits::address_t)current_image->getPixelAddress(frame_x, frame_y + y);
|
|
|
|
|
2012-01-06 06:45:03 +08:00
|
|
|
if (EGifPutLine(gif_file, addr, frame_w) == GIF_ERROR)
|
2014-02-18 08:43:20 +08:00
|
|
|
throw Exception("Error writing GIF image scanlines for frame %d.\n", (int)frame_num);
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-28 22:06:11 +08:00
|
|
|
copy_image(previous_image, current_image);
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
2007-09-30 23:32:21 +08:00
|
|
|
|
2011-01-19 07:42:43 +08:00
|
|
|
return true;
|
2014-04-10 11:33:28 +08:00
|
|
|
#endif
|
2015-06-09 07:21:50 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
2015-04-01 23:35:18 +08:00
|
|
|
base::SharedPtr<FormatOptions> GifFormat::onGetFormatOptions(FileOp* fop)
|
2014-07-20 09:01:39 +08:00
|
|
|
{
|
2015-04-01 23:35:18 +08:00
|
|
|
base::SharedPtr<GifOptions> gif_options;
|
2015-04-03 07:42:43 +08:00
|
|
|
if (fop->document->getFormatOptions())
|
2015-04-01 23:35:18 +08:00
|
|
|
gif_options = base::SharedPtr<GifOptions>(fop->document->getFormatOptions());
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
if (!gif_options)
|
|
|
|
gif_options.reset(new GifOptions);
|
|
|
|
|
|
|
|
// Non-interactive mode
|
2015-05-19 04:04:31 +08:00
|
|
|
if (!fop->context || !fop->context->isUIAvailable())
|
2014-07-20 09:01:39 +08:00
|
|
|
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()));
|
2015-01-29 22:24:43 +08:00
|
|
|
gif_options->setLoop(get_config_bool("GIF", "Loop", gif_options->loop()));
|
2014-10-21 09:21:31 +08:00
|
|
|
gif_options->setDithering((doc::DitheringMethod)get_config_int("GIF", "Dither", (int)gif_options->dithering()));
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
// Load the window to ask to the user the GIF options he wants.
|
|
|
|
|
|
|
|
app::gen::GifOptions win;
|
2014-07-30 12:28:15 +08:00
|
|
|
win.rgbOptions()->setVisible(fop->document->sprite()->pixelFormat() != IMAGE_INDEXED);
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
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());
|
2015-01-29 22:24:43 +08:00
|
|
|
win.loop()->setSelected(gif_options->loop());
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
win.dither()->setEnabled(true);
|
2014-12-28 22:06:11 +08:00
|
|
|
win.dither()->setSelected(gif_options->dithering() == doc::DitheringMethod::ORDERED);
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
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());
|
2015-01-29 22:24:43 +08:00
|
|
|
gif_options->setLoop(win.loop()->isSelected());
|
2014-07-20 09:01:39 +08:00
|
|
|
gif_options->setDithering(win.dither()->isSelected() ?
|
2014-12-28 22:06:11 +08:00
|
|
|
doc::DitheringMethod::ORDERED:
|
|
|
|
doc::DitheringMethod::NONE);
|
2014-07-20 09:01:39 +08:00
|
|
|
|
|
|
|
set_config_int("GIF", "Quantize", gif_options->quantize());
|
|
|
|
set_config_bool("GIF", "Interlaced", gif_options->interlaced());
|
2015-01-29 22:24:43 +08:00
|
|
|
set_config_bool("GIF", "Loop", gif_options->loop());
|
2014-12-28 22:06:11 +08:00
|
|
|
set_config_int("GIF", "Dither", int(gif_options->dithering()));
|
2014-07-20 09:01:39 +08:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
gif_options.reset(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return gif_options;
|
|
|
|
}
|
|
|
|
catch (std::exception& e) {
|
|
|
|
Console::showException(e);
|
2015-04-01 23:35:18 +08:00
|
|
|
return base::SharedPtr<GifOptions>(0);
|
2014-07-20 09:01:39 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|