Add option to export GIF for Twitter (#1220, #1252)

This commit is contained in:
David Capello 2018-03-16 11:03:50 -03:00
parent d9a848a32c
commit 1fb463f931
8 changed files with 79 additions and 4 deletions

View File

@ -359,6 +359,7 @@
<option id="frame_tag" type="std::string" /> <option id="frame_tag" type="std::string" />
<option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" /> <option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" />
<option id="apply_pixel_ratio" type="bool" default="false" /> <option id="apply_pixel_ratio" type="bool" default="false" />
<option id="for_twitter" type="bool" default="false" />
</section> </section>
<section id="sprite_sheet"> <section id="sprite_sheet">
<option id="defined" type="bool" default="false" /> <option id="defined" type="bool" default="false" />

View File

@ -463,6 +463,11 @@ layers = Layers:
frames = Frames: frames = Frames:
anidir = Animation Direction: anidir = Animation Direction:
pixel_ratio = Apply pixel ratio pixel_ratio = Apply pixel ratio
for_twitter = Export for Twitter
for_twitter_tooltip = <<<END
Adjust the duration of the last frame to 1/4 so
Twitter reproduces the animation correctly.
END
export = &Export export = &Export
cancel = &Cancel cancel = &Cancel

View File

@ -34,6 +34,8 @@
<check id="pixel_ratio" text="@.pixel_ratio" cell_hspan="3" /> <check id="pixel_ratio" text="@.pixel_ratio" cell_hspan="3" />
<check id="for_twitter" text="@.for_twitter" tooltip="@.for_twitter_tooltip" cell_hspan="3" />
<hbox cell_hspan="3"> <hbox cell_hspan="3">
<boxfiller /> <boxfiller />
<hbox homogeneous="true"> <hbox homogeneous="true">

View File

@ -17,6 +17,7 @@
#include "app/console.h" #include "app/console.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/file/file.h" #include "app/file/file.h"
#include "app/file/gif_format.h"
#include "app/file_selector.h" #include "app/file_selector.h"
#include "app/i18n/strings.h" #include "app/i18n/strings.h"
#include "app/job.h" #include "app/job.h"
@ -386,6 +387,8 @@ again:;
case doc::AniDir::PING_PONG: m_aniDir = "ping-pong"; break; case doc::AniDir::PING_PONG: m_aniDir = "ping-pong"; break;
} }
GifEncoderDurationFix fix(win.isForTwitter());
saveDocumentInBackground( saveDocumentInBackground(
context, doc, outputFilename, false); context, doc, outputFilename, false);

View File

@ -14,6 +14,7 @@
#include "app/file/file.h" #include "app/file/file.h"
#include "app/file/file_format.h" #include "app/file/file_format.h"
#include "app/file/format_options.h" #include "app/file/format_options.h"
#include "app/file/gif_format.h"
#include "app/file/gif_options.h" #include "app/file/gif_options.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
@ -102,6 +103,21 @@ FileFormat* CreateGifFormat()
static int interlaced_offset[] = { 0, 4, 2, 1 }; static int interlaced_offset[] = { 0, 4, 2, 1 };
static int interlaced_jumps[] = { 8, 8, 4, 2 }; static int interlaced_jumps[] = { 8, 8, 4, 2 };
// True if the GifEncoder should save the animation for Twitter:
// * Frames duration >= 2, and
// * Last frame 1/4 of its duration
static bool fix_last_frame_duration = false;
GifEncoderDurationFix::GifEncoderDurationFix(bool state)
{
fix_last_frame_duration = state;
}
GifEncoderDurationFix::~GifEncoderDurationFix()
{
fix_last_frame_duration = false;
}
struct GifFilePtr { struct GifFilePtr {
public: public:
#if GIFLIB_MAJOR >= 5 #if GIFLIB_MAJOR >= 5
@ -967,7 +983,9 @@ public:
if (frameBounds.isEmpty()) if (frameBounds.isEmpty())
frameBounds = gfx::Rect(0, 0, 1, 1); frameBounds = gfx::Rect(0, 0, 1, 1);
writeImage(gifFrame, frame, frameBounds, disposal); writeImage(gifFrame, frame, frameBounds, disposal,
// Only the last frame in the animation needs the fix
(fix_last_frame_duration && gifFrame == nframes-1));
// Dispose/clear frame content // Dispose/clear frame content
process_disposal_method(m_previousImage, process_disposal_method(m_previousImage,
@ -1035,10 +1053,22 @@ private:
// Writes graphics extension record (to save the duration of the // Writes graphics extension record (to save the duration of the
// frame and maybe the transparency index). // frame and maybe the transparency index).
void writeExtension(gifframe_t gifFrame, frame_t frame, int transparentIndex, DisposalMethod disposalMethod) { void writeExtension(const gifframe_t gifFrame,
const frame_t frame,
const int transparentIndex,
const DisposalMethod disposalMethod,
const bool fixDuration) {
unsigned char extension_bytes[5]; unsigned char extension_bytes[5];
int frameDelay = m_sprite->frameDuration(frame) / 10; int frameDelay = m_sprite->frameDuration(frame) / 10;
// Fix duration for Twitter. It looks like the last frame must be
// 1/4 of its duration for some strange reason in the Twitter
// conversion from GIF to video.
if (fixDuration)
frameDelay = MAX(2, frameDelay/4);
if (fix_last_frame_duration)
frameDelay = MAX(2, frameDelay);
extension_bytes[0] = (((int(disposalMethod) & 7) << 2) | extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
(transparentIndex >= 0 ? 1: 0)); (transparentIndex >= 0 ? 1: 0));
extension_bytes[1] = (frameDelay & 0xff); extension_bytes[1] = (frameDelay & 0xff);
@ -1106,7 +1136,11 @@ private:
} }
} }
void writeImage(gifframe_t gifFrame, frame_t frame, const gfx::Rect& frameBounds, DisposalMethod disposal) { void writeImage(const gifframe_t gifFrame,
const frame_t frame,
const gfx::Rect& frameBounds,
const DisposalMethod disposal,
const bool fixDuration) {
UniquePtr<Palette> framePaletteRef; UniquePtr<Palette> framePaletteRef;
UniquePtr<RgbMap> rgbmapRef; UniquePtr<RgbMap> rgbmapRef;
Palette* framePalette = m_sprite->palette(frame); Palette* framePalette = m_sprite->palette(frame);
@ -1227,7 +1261,8 @@ private:
remap.map(m_transparentIndex, localTransparent); remap.map(m_transparentIndex, localTransparent);
// Write extension record. // Write extension record.
writeExtension(gifFrame, frame, localTransparent, disposal); writeExtension(gifFrame, frame, localTransparent,
disposal, fixDuration);
// Write the image record. // Write the image record.
if (EGifPutImageDesc(m_gifFile, if (EGifPutImageDesc(m_gifFile,

21
src/app/file/gif_format.h Normal file
View File

@ -0,0 +1,21 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_FILE_GIF_FORMAT_H_INCLUDED
#define APP_FILE_GIF_FORMAT_H_INCLUDED
#pragma once
namespace app {
class GifEncoderDurationFix {
public:
GifEncoderDurationFix(bool state);
~GifEncoderDurationFix();
};
} // namespace app
#endif

View File

@ -49,6 +49,7 @@ ExportFileWindow::ExportFileWindow(const Document* doc)
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag()); fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir()); fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio()); pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio());
forTwitter()->setSelected(m_docPref.saveCopy.forTwitter());
updateAniDir(); updateAniDir();
@ -83,6 +84,7 @@ void ExportFileWindow::savePref()
m_docPref.saveCopy.layer(layersValue()); m_docPref.saveCopy.layer(layersValue());
m_docPref.saveCopy.frameTag(framesValue()); m_docPref.saveCopy.frameTag(framesValue());
m_docPref.saveCopy.applyPixelRatio(applyPixelRatio()); m_docPref.saveCopy.applyPixelRatio(applyPixelRatio());
m_docPref.saveCopy.forTwitter(isForTwitter());
} }
std::string ExportFileWindow::outputFilenameValue() const std::string ExportFileWindow::outputFilenameValue() const
@ -116,6 +118,11 @@ bool ExportFileWindow::applyPixelRatio() const
return pixelRatio()->isSelected(); return pixelRatio()->isSelected();
} }
bool ExportFileWindow::isForTwitter() const
{
return forTwitter()->isSelected();
}
void ExportFileWindow::setOutputFilename(const std::string& pathAndFilename) void ExportFileWindow::setOutputFilename(const std::string& pathAndFilename)
{ {
m_outputPath = base::get_file_path(pathAndFilename); m_outputPath = base::get_file_path(pathAndFilename);

View File

@ -31,6 +31,7 @@ namespace app {
std::string framesValue() const; std::string framesValue() const;
doc::AniDir aniDirValue() const; doc::AniDir aniDirValue() const;
bool applyPixelRatio() const; bool applyPixelRatio() const;
bool isForTwitter() const;
obs::signal<std::string()> SelectOutputFile; obs::signal<std::string()> SelectOutputFile;