mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 05:42:32 +00:00
parent
d9a848a32c
commit
1fb463f931
@ -359,6 +359,7 @@
|
||||
<option id="frame_tag" type="std::string" />
|
||||
<option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" />
|
||||
<option id="apply_pixel_ratio" type="bool" default="false" />
|
||||
<option id="for_twitter" type="bool" default="false" />
|
||||
</section>
|
||||
<section id="sprite_sheet">
|
||||
<option id="defined" type="bool" default="false" />
|
||||
|
@ -463,6 +463,11 @@ layers = Layers:
|
||||
frames = Frames:
|
||||
anidir = Animation Direction:
|
||||
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
|
||||
cancel = &Cancel
|
||||
|
||||
|
@ -34,6 +34,8 @@
|
||||
|
||||
<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">
|
||||
<boxfiller />
|
||||
<hbox homogeneous="true">
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "app/console.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/gif_format.h"
|
||||
#include "app/file_selector.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/job.h"
|
||||
@ -386,6 +387,8 @@ again:;
|
||||
case doc::AniDir::PING_PONG: m_aniDir = "ping-pong"; break;
|
||||
}
|
||||
|
||||
GifEncoderDurationFix fix(win.isForTwitter());
|
||||
|
||||
saveDocumentInBackground(
|
||||
context, doc, outputFilename, false);
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "app/file/file.h"
|
||||
#include "app/file/file_format.h"
|
||||
#include "app/file/format_options.h"
|
||||
#include "app/file/gif_format.h"
|
||||
#include "app/file/gif_options.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
@ -102,6 +103,21 @@ FileFormat* CreateGifFormat()
|
||||
static int interlaced_offset[] = { 0, 4, 2, 1 };
|
||||
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 {
|
||||
public:
|
||||
#if GIFLIB_MAJOR >= 5
|
||||
@ -967,7 +983,9 @@ public:
|
||||
if (frameBounds.isEmpty())
|
||||
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
|
||||
process_disposal_method(m_previousImage,
|
||||
@ -1035,10 +1053,22 @@ private:
|
||||
|
||||
// Writes graphics extension record (to save the duration of the
|
||||
// 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];
|
||||
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) |
|
||||
(transparentIndex >= 0 ? 1: 0));
|
||||
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<RgbMap> rgbmapRef;
|
||||
Palette* framePalette = m_sprite->palette(frame);
|
||||
@ -1227,7 +1261,8 @@ private:
|
||||
remap.map(m_transparentIndex, localTransparent);
|
||||
|
||||
// Write extension record.
|
||||
writeExtension(gifFrame, frame, localTransparent, disposal);
|
||||
writeExtension(gifFrame, frame, localTransparent,
|
||||
disposal, fixDuration);
|
||||
|
||||
// Write the image record.
|
||||
if (EGifPutImageDesc(m_gifFile,
|
||||
|
21
src/app/file/gif_format.h
Normal file
21
src/app/file/gif_format.h
Normal 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
|
@ -49,6 +49,7 @@ ExportFileWindow::ExportFileWindow(const Document* doc)
|
||||
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
|
||||
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
|
||||
pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio());
|
||||
forTwitter()->setSelected(m_docPref.saveCopy.forTwitter());
|
||||
|
||||
updateAniDir();
|
||||
|
||||
@ -83,6 +84,7 @@ void ExportFileWindow::savePref()
|
||||
m_docPref.saveCopy.layer(layersValue());
|
||||
m_docPref.saveCopy.frameTag(framesValue());
|
||||
m_docPref.saveCopy.applyPixelRatio(applyPixelRatio());
|
||||
m_docPref.saveCopy.forTwitter(isForTwitter());
|
||||
}
|
||||
|
||||
std::string ExportFileWindow::outputFilenameValue() const
|
||||
@ -116,6 +118,11 @@ bool ExportFileWindow::applyPixelRatio() const
|
||||
return pixelRatio()->isSelected();
|
||||
}
|
||||
|
||||
bool ExportFileWindow::isForTwitter() const
|
||||
{
|
||||
return forTwitter()->isSelected();
|
||||
}
|
||||
|
||||
void ExportFileWindow::setOutputFilename(const std::string& pathAndFilename)
|
||||
{
|
||||
m_outputPath = base::get_file_path(pathAndFilename);
|
||||
|
@ -31,6 +31,7 @@ namespace app {
|
||||
std::string framesValue() const;
|
||||
doc::AniDir aniDirValue() const;
|
||||
bool applyPixelRatio() const;
|
||||
bool isForTwitter() const;
|
||||
|
||||
obs::signal<std::string()> SelectOutputFile;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user