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="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" />
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
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_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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user