mirror of https://github.com/aseprite/aseprite.git
Fix exporting selection to gif/fli/webp files (fix #3827)
This commit is contained in:
parent
bd91a6430f
commit
35e64ad2f3
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2016-2017 David Capello
|
// Copyright (C) 2016-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -28,7 +28,7 @@ FileOpROI CliOpenFile::roi() const
|
||||||
selFrames.insert(fromFrame, toFrame);
|
selFrames.insert(fromFrame, toFrame);
|
||||||
|
|
||||||
return FileOpROI(document,
|
return FileOpROI(document,
|
||||||
gfx::Rect(),
|
document->sprite()->bounds(),
|
||||||
slice,
|
slice,
|
||||||
tag,
|
tag,
|
||||||
selFrames,
|
selFrames,
|
||||||
|
|
|
@ -228,8 +228,14 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx::Rect bounds;
|
gfx::Rect bounds;
|
||||||
if (params().bounds.isSet())
|
if (params().bounds.isSet()) {
|
||||||
|
// Export the specific given bounds (e.g. the selection bounds)
|
||||||
bounds = params().bounds();
|
bounds = params().bounds();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Export the whole sprite canvas.
|
||||||
|
bounds = document->sprite()->bounds();
|
||||||
|
}
|
||||||
|
|
||||||
FileOpROI roi(document, bounds,
|
FileOpROI roi(document, bounds,
|
||||||
params().slice(), params().tag(),
|
params().slice(), params().tag(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -1089,9 +1089,10 @@ bool BmpFormat::onLoad(FileOp *fop)
|
||||||
else
|
else
|
||||||
rmask = gmask = bmask = amask = 0;
|
rmask = gmask = bmask = amask = 0;
|
||||||
|
|
||||||
ImageRef image = fop->sequenceImage(pixelFormat,
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
infoheader.biWidth,
|
pixelFormat,
|
||||||
ABS((int)infoheader.biHeight));
|
infoheader.biWidth,
|
||||||
|
ABS((int)infoheader.biHeight));
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1166,7 +1167,7 @@ bool BmpFormat::onLoad(FileOp *fop)
|
||||||
#ifdef ENABLE_SAVE
|
#ifdef ENABLE_SAVE
|
||||||
bool BmpFormat::onSave(FileOp *fop)
|
bool BmpFormat::onSave(FileOp *fop)
|
||||||
{
|
{
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
const ImageSpec spec = img->spec();
|
const ImageSpec spec = img->spec();
|
||||||
const int w = spec.width();
|
const int w = spec.width();
|
||||||
const int h = spec.height();
|
const int h = spec.height();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
// Copyright (c) 2018-2023 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
|
@ -88,7 +88,7 @@ bool CssFormat::onLoad(FileOp* fop)
|
||||||
|
|
||||||
bool CssFormat::onSave(FileOp* fop)
|
bool CssFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const ImageRef image = fop->sequenceImage();
|
const ImageRef image = fop->sequenceImageToSave();
|
||||||
int x, y, c, r, g, b, a, alpha;
|
int x, y, c, r, g, b, a, alpha;
|
||||||
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
#include "ask_for_color_profile.xml.h"
|
#include "ask_for_color_profile.xml.h"
|
||||||
#include "open_sequence.xml.h"
|
#include "open_sequence.xml.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
|
||||||
|
@ -60,24 +61,39 @@ public:
|
||||||
: m_doc(fop->document())
|
: m_doc(fop->document())
|
||||||
, m_sprite(m_doc->sprite())
|
, m_sprite(m_doc->sprite())
|
||||||
, m_spec(m_sprite->spec())
|
, m_spec(m_sprite->spec())
|
||||||
, m_newBlend(fop->newBlend()) {
|
, m_supportAnimation(fop->fileFormat()->support(FILE_SUPPORT_FRAMES))
|
||||||
|
, m_newBlend(fop->newBlend())
|
||||||
|
{
|
||||||
ASSERT(m_doc && m_sprite);
|
ASSERT(m_doc && m_sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSpecSize(const gfx::Size& size) {
|
void setSpecSize(const gfx::Size& fullCanvasSize,
|
||||||
m_spec.setWidth(size.w * m_scale.x);
|
const gfx::Size& frameSize) {
|
||||||
m_spec.setHeight(size.h * m_scale.y);
|
if (m_supportAnimation) {
|
||||||
|
m_spec.setSize(std::max<int>(1, fullCanvasSize.w*m_scale.x),
|
||||||
|
std::max<int>(1, fullCanvasSize.h*m_scale.y));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_spec.setSize(std::max<int>(1, frameSize.w*m_scale.x),
|
||||||
|
std::max<int>(1, frameSize.h*m_scale.y));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUnscaledImage(const doc::frame_t frame,
|
void setUnscaledImageToSave(const doc::frame_t frame,
|
||||||
const doc::ImageRef& image) {
|
const doc::ImageRef& image) {
|
||||||
if (m_spec.width() == image->width() &&
|
// If we don't need to rescale the input "image", we can just
|
||||||
m_spec.height() == image->height()) {
|
// reference the same exact image to encode (as we don't need to
|
||||||
|
// call resize_image()).
|
||||||
|
if (!needResize()) {
|
||||||
m_tmpScaledImage = image;
|
m_tmpScaledImage = image;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!m_tmpScaledImage)
|
// In other case we need to create a temporal image to resize
|
||||||
|
// the input "image" to "m_tmpScaledImage" for the encoder.
|
||||||
|
if (!m_tmpScaledImage ||
|
||||||
|
m_tmpScaledImage->spec() != m_spec) {
|
||||||
m_tmpScaledImage.reset(doc::Image::create(m_spec));
|
m_tmpScaledImage.reset(doc::Image::create(m_spec));
|
||||||
|
}
|
||||||
|
|
||||||
doc::algorithm::resize_image(
|
doc::algorithm::resize_image(
|
||||||
image.get(),
|
image.get(),
|
||||||
|
@ -132,13 +148,16 @@ public:
|
||||||
return m_tmpScaledImage->getPixelAddress(0, y);
|
return m_tmpScaledImage->getPixelAddress(0, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderFrame(const doc::frame_t frame, doc::Image* dst) const override {
|
void renderFrame(const doc::frame_t frame,
|
||||||
const bool needResize =
|
const gfx::Rect& frameBounds,
|
||||||
(dst->width() != m_sprite->width() ||
|
doc::Image* dst) const override {
|
||||||
dst->height() != m_sprite->height());
|
const bool needResize = this->needResize();
|
||||||
|
|
||||||
if (needResize && !m_tmpUnscaledRender) {
|
if (needResize &&
|
||||||
|
(!m_tmpUnscaledRender ||
|
||||||
|
m_tmpUnscaledRender->size() != frameBounds.size())) {
|
||||||
auto spec = m_sprite->spec();
|
auto spec = m_sprite->spec();
|
||||||
|
spec.setSize(frameBounds.size());
|
||||||
spec.setColorMode(dst->colorMode());
|
spec.setColorMode(dst->colorMode());
|
||||||
m_tmpUnscaledRender.reset(doc::Image::create(spec));
|
m_tmpUnscaledRender.reset(doc::Image::create(spec));
|
||||||
}
|
}
|
||||||
|
@ -148,7 +167,8 @@ public:
|
||||||
render.setBgOptions(render::BgOptions::MakeNone());
|
render.setBgOptions(render::BgOptions::MakeNone());
|
||||||
render.renderSprite(
|
render.renderSprite(
|
||||||
(needResize ? m_tmpUnscaledRender.get(): dst),
|
(needResize ? m_tmpUnscaledRender.get(): dst),
|
||||||
m_sprite, frame);
|
m_sprite, frame,
|
||||||
|
gfx::Clip(gfx::Point(0, 0), frameBounds));
|
||||||
|
|
||||||
if (needResize) {
|
if (needResize) {
|
||||||
doc::algorithm::resize_image(
|
doc::algorithm::resize_image(
|
||||||
|
@ -168,10 +188,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool needResize() const {
|
||||||
|
return (m_scale != gfx::PointF(1.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
const Doc* m_doc;
|
const Doc* m_doc;
|
||||||
const doc::Sprite* m_sprite;
|
const doc::Sprite* m_sprite;
|
||||||
doc::ImageSpec m_spec;
|
doc::ImageSpec m_spec;
|
||||||
bool m_newBlend;
|
const bool m_supportAnimation;
|
||||||
|
const bool m_newBlend;
|
||||||
doc::ImageRef m_tmpScaledImage = nullptr;
|
doc::ImageRef m_tmpScaledImage = nullptr;
|
||||||
mutable doc::ImageRef m_tmpUnscaledRender = nullptr;
|
mutable doc::ImageRef m_tmpUnscaledRender = nullptr;
|
||||||
gfx::PointF m_scale = gfx::PointF(1.0, 1.0);
|
gfx::PointF m_scale = gfx::PointF(1.0, 1.0);
|
||||||
|
@ -232,7 +257,8 @@ int save_document(Context* context, Doc* document)
|
||||||
std::unique_ptr<FileOp> fop(
|
std::unique_ptr<FileOp> fop(
|
||||||
FileOp::createSaveDocumentOperation(
|
FileOp::createSaveDocumentOperation(
|
||||||
context,
|
context,
|
||||||
FileOpROI(document, gfx::Rect(), "", "", SelectedFrames(), false),
|
FileOpROI(document, document->sprite()->bounds(),
|
||||||
|
"", "", SelectedFrames(), false),
|
||||||
document->filename(), "",
|
document->filename(), "",
|
||||||
false));
|
false));
|
||||||
if (!fop)
|
if (!fop)
|
||||||
|
@ -303,6 +329,37 @@ FileOpROI::FileOpROI(const Doc* doc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfx::Rect FileOpROI::frameBounds(const frame_t frame) const
|
||||||
|
{
|
||||||
|
// Export bounds of specific slice
|
||||||
|
if (m_slice) {
|
||||||
|
const SliceKey* key = m_slice->getByFrame(frame);
|
||||||
|
if (!key || key->isEmpty())
|
||||||
|
return gfx::Rect(); // Return an empty rectangle
|
||||||
|
|
||||||
|
return key->bounds();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Export specific bounds
|
||||||
|
ASSERT(!m_bounds.isEmpty());
|
||||||
|
return m_bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::Size FileOpROI::fileCanvasSize() const
|
||||||
|
{
|
||||||
|
if (m_slice) {
|
||||||
|
gfx::Size size;
|
||||||
|
for (auto frame : m_selFrames)
|
||||||
|
size |= frameBounds(frame).size();
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ASSERT(!m_bounds.isEmpty());
|
||||||
|
return m_bounds.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
FileOp* FileOp::createLoadDocumentOperation(Context* context,
|
FileOp* FileOp::createLoadDocumentOperation(Context* context,
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
|
@ -859,7 +916,7 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||||
}
|
}
|
||||||
// We don't need this image
|
// We don't need this image
|
||||||
else {
|
else {
|
||||||
delete m_seq.image;
|
m_seq.image.reset();
|
||||||
|
|
||||||
// But add a link frame
|
// But add a link frame
|
||||||
m_seq.last_cel->image = image_index;
|
m_seq.last_cel->image = image_index;
|
||||||
|
@ -949,8 +1006,8 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||||
|
|
||||||
// Create a temporary bitmap
|
// Create a temporary bitmap
|
||||||
m_seq.image.reset(Image::create(sprite->pixelFormat(),
|
m_seq.image.reset(Image::create(sprite->pixelFormat(),
|
||||||
sprite->width(),
|
m_roi.fileCanvasSize().w,
|
||||||
sprite->height()));
|
m_roi.fileCanvasSize().h));
|
||||||
|
|
||||||
m_seq.progress_offset = 0.0f;
|
m_seq.progress_offset = 0.0f;
|
||||||
m_seq.progress_fraction = 1.0f / (double)sprite->totalFrames();
|
m_seq.progress_fraction = 1.0f / (double)sprite->totalFrames();
|
||||||
|
@ -961,39 +1018,19 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||||
|
|
||||||
frame_t outputFrame = 0;
|
frame_t outputFrame = 0;
|
||||||
for (frame_t frame : m_roi.selectedFrames()) {
|
for (frame_t frame : m_roi.selectedFrames()) {
|
||||||
gfx::Rect bounds;
|
gfx::Rect bounds = m_roi.frameBounds(frame);
|
||||||
|
if (bounds.isEmpty())
|
||||||
|
continue; // Skip frame because there is no slice key
|
||||||
|
|
||||||
// Export bounds of specific slice
|
if (m_abstractImage) {
|
||||||
if (m_roi.slice()) {
|
m_abstractImage->setSpecSize(m_roi.fileCanvasSize(),
|
||||||
const SliceKey* key = m_roi.slice()->getByFrame(frame);
|
bounds.size());
|
||||||
if (!key || key->isEmpty())
|
|
||||||
continue; // Skip frame because there is no slice key
|
|
||||||
|
|
||||||
bounds = key->bounds();
|
|
||||||
}
|
|
||||||
// Export specific bounds
|
|
||||||
else if (!m_roi.bounds().isEmpty()) {
|
|
||||||
bounds = m_roi.bounds();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the "frame" in "m_seq.image" with the given bounds
|
// Render the (unscaled) sequenced image.
|
||||||
// (bounds can be the selection bounds or a slice key bounds)
|
render.renderSprite(
|
||||||
if (!bounds.isEmpty()) {
|
m_seq.image.get(), sprite, frame,
|
||||||
if (m_abstractImage)
|
gfx::Clip(gfx::Point(0, 0), bounds));
|
||||||
m_abstractImage->setSpecSize(bounds.size());
|
|
||||||
|
|
||||||
m_seq.image.reset(
|
|
||||||
Image::create(sprite->pixelFormat(),
|
|
||||||
bounds.w,
|
|
||||||
bounds.h));
|
|
||||||
|
|
||||||
render.renderSprite(
|
|
||||||
m_seq.image.get(), sprite, frame,
|
|
||||||
gfx::Clip(gfx::Point(0, 0), bounds));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
render.renderSprite(m_seq.image.get(), sprite, frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool save = true;
|
bool save = true;
|
||||||
|
|
||||||
|
@ -1035,6 +1072,11 @@ void FileOp::operate(IFileOpProgress* progress)
|
||||||
else {
|
else {
|
||||||
makeDirectories();
|
makeDirectories();
|
||||||
|
|
||||||
|
if (m_abstractImage) {
|
||||||
|
m_abstractImage->setSpecSize(m_roi.fileCanvasSize(),
|
||||||
|
m_roi.fileCanvasSize());
|
||||||
|
}
|
||||||
|
|
||||||
// Call the "save" procedure.
|
// Call the "save" procedure.
|
||||||
if (!m_format->save(this)) {
|
if (!m_format->save(this)) {
|
||||||
setError("Error saving the sprite in the file \"%s\"\n",
|
setError("Error saving the sprite in the file \"%s\"\n",
|
||||||
|
@ -1308,7 +1350,9 @@ void FileOp::sequenceGetAlpha(int index, int* a) const
|
||||||
*a = 0;
|
*a = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageRef FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
|
ImageRef FileOp::sequenceImageToLoad(
|
||||||
|
const PixelFormat pixelFormat,
|
||||||
|
const int w, const int h)
|
||||||
{
|
{
|
||||||
Sprite* sprite;
|
Sprite* sprite;
|
||||||
|
|
||||||
|
@ -1340,7 +1384,7 @@ ImageRef FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_seq.last_cel) {
|
if (m_seq.last_cel) {
|
||||||
setError("Error: called two times FileOp::sequenceImage()\n");
|
setError("Error: called two times FileOp::sequenceImageToLoad()\n");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1358,15 +1402,17 @@ void FileOp::makeAbstractImage()
|
||||||
m_abstractImage = std::make_unique<FileAbstractImageImpl>(this);
|
m_abstractImage = std::make_unique<FileAbstractImageImpl>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileAbstractImage* FileOp::abstractImage()
|
FileAbstractImage* FileOp::abstractImageToSave()
|
||||||
{
|
{
|
||||||
ASSERT(m_format->support(FILE_ENCODE_ABSTRACT_IMAGE));
|
ASSERT(m_format->support(FILE_ENCODE_ABSTRACT_IMAGE));
|
||||||
|
|
||||||
makeAbstractImage();
|
makeAbstractImage();
|
||||||
|
|
||||||
// Use sequenceImage() to fill the current image
|
// Use sequenceImageToSave() to fill the current image
|
||||||
if (m_format->support(FILE_SUPPORT_SEQUENCES))
|
if (m_format->support(FILE_SUPPORT_SEQUENCES)) {
|
||||||
m_abstractImage->setUnscaledImage(m_seq.frame, sequenceImage());
|
m_abstractImage->setUnscaledImageToSave(m_seq.frame++,
|
||||||
|
m_seq.image);
|
||||||
|
}
|
||||||
|
|
||||||
return m_abstractImage.get();
|
return m_abstractImage.get();
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@ namespace app {
|
||||||
const bool adjustByTag);
|
const bool adjustByTag);
|
||||||
|
|
||||||
const Doc* document() const { return m_document; }
|
const Doc* document() const { return m_document; }
|
||||||
const gfx::Rect& bounds() const { return m_bounds; }
|
|
||||||
doc::Slice* slice() const { return m_slice; }
|
doc::Slice* slice() const { return m_slice; }
|
||||||
doc::Tag* tag() const { return m_tag; }
|
doc::Tag* tag() const { return m_tag; }
|
||||||
doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); }
|
doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); }
|
||||||
|
@ -90,6 +89,15 @@ namespace app {
|
||||||
return (doc::frame_t)m_selFrames.size();
|
return (doc::frame_t)m_selFrames.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns an empty rectangle only when exporting a slice and the
|
||||||
|
// slice doesn't have a slice key in this specific frame.
|
||||||
|
gfx::Rect frameBounds(const frame_t frame) const;
|
||||||
|
|
||||||
|
// Canvas size required to store all frames (e.g. if a slice
|
||||||
|
// changes size on each frame, we have to keep the biggest of
|
||||||
|
// those sizes).
|
||||||
|
gfx::Size fileCanvasSize() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Doc* m_document;
|
const Doc* m_document;
|
||||||
gfx::Rect m_bounds;
|
gfx::Rect m_bounds;
|
||||||
|
@ -108,6 +116,7 @@ namespace app {
|
||||||
virtual int width() const { return spec().width(); }
|
virtual int width() const { return spec().width(); }
|
||||||
virtual int height() const { return spec().height(); }
|
virtual int height() const { return spec().height(); }
|
||||||
|
|
||||||
|
// Spec (width/height) to save the file.
|
||||||
virtual const doc::ImageSpec& spec() const = 0;
|
virtual const doc::ImageSpec& spec() const = 0;
|
||||||
virtual os::ColorSpaceRef osColorSpace() const = 0;
|
virtual os::ColorSpaceRef osColorSpace() const = 0;
|
||||||
virtual bool needAlpha() const = 0;
|
virtual bool needAlpha() const = 0;
|
||||||
|
@ -118,15 +127,21 @@ namespace app {
|
||||||
virtual const doc::Palette* palette(doc::frame_t frame) const = 0;
|
virtual const doc::Palette* palette(doc::frame_t frame) const = 0;
|
||||||
virtual doc::PalettesList palettes() const = 0;
|
virtual doc::PalettesList palettes() const = 0;
|
||||||
|
|
||||||
|
// Returns the whole image to be saved (for encoders that needs
|
||||||
|
// all the rows at once).
|
||||||
virtual const doc::ImageRef getScaledImage() const = 0;
|
virtual const doc::ImageRef getScaledImage() const = 0;
|
||||||
|
|
||||||
// In case the file format can encode scanline by scanline
|
// In case that the file format can encode scanline by scanline
|
||||||
// (e.g. PNG format).
|
// (e.g. PNG format) we can request each row to encode (without
|
||||||
|
// the need to call getScaledImage()). Each scanline depends on
|
||||||
|
// the spec() width.
|
||||||
virtual const uint8_t* getScanline(int y) const = 0;
|
virtual const uint8_t* getScanline(int y) const = 0;
|
||||||
|
|
||||||
// In case that the encoder needs full frame renders (or compare
|
// In case that the encoder supports animation and needs to render
|
||||||
// between frames), e.g. GIF format.
|
// a full frame renders.
|
||||||
virtual void renderFrame(const doc::frame_t frame, doc::Image* dst) const = 0;
|
virtual void renderFrame(const doc::frame_t frame,
|
||||||
|
const gfx::Rect& frameBounds,
|
||||||
|
doc::Image* dst) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Structure to load & save files.
|
// Structure to load & save files.
|
||||||
|
@ -155,6 +170,7 @@ namespace app {
|
||||||
bool isSequence() const { return !m_seq.filename_list.empty(); }
|
bool isSequence() const { return !m_seq.filename_list.empty(); }
|
||||||
bool isOneFrame() const { return m_oneframe; }
|
bool isOneFrame() const { return m_oneframe; }
|
||||||
bool preserveColorProfile() const { return m_config.preserveColorProfile; }
|
bool preserveColorProfile() const { return m_config.preserveColorProfile; }
|
||||||
|
const FileFormat* fileFormat() const { return m_format; }
|
||||||
|
|
||||||
const std::string& filename() const { return m_filename; }
|
const std::string& filename() const { return m_filename; }
|
||||||
const base::paths& filenames() const { return m_seq.filename_list; }
|
const base::paths& filenames() const { return m_seq.filename_list; }
|
||||||
|
@ -227,8 +243,8 @@ namespace app {
|
||||||
void sequenceGetColor(int index, int* r, int* g, int* b) const;
|
void sequenceGetColor(int index, int* r, int* g, int* b) const;
|
||||||
void sequenceSetAlpha(int index, int a);
|
void sequenceSetAlpha(int index, int a);
|
||||||
void sequenceGetAlpha(int index, int* a) const;
|
void sequenceGetAlpha(int index, int* a) const;
|
||||||
ImageRef sequenceImage(PixelFormat pixelFormat, int w, int h);
|
ImageRef sequenceImageToLoad(PixelFormat pixelFormat, int w, int h);
|
||||||
const ImageRef sequenceImage() const { return m_seq.image; }
|
const ImageRef sequenceImageToSave() const { return m_seq.image; }
|
||||||
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
||||||
bool sequenceGetHasAlpha() const {
|
bool sequenceGetHasAlpha() const {
|
||||||
return m_seq.has_alpha;
|
return m_seq.has_alpha;
|
||||||
|
@ -241,8 +257,12 @@ namespace app {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can be used to encode sequences/static files (e.g. png files)
|
// Can be used to encode sequences/static files (e.g. png files)
|
||||||
// or animations (e.g. gif) resizing the result on the fly.
|
// or animations (e.g. gif) resizing the result on the fly. This
|
||||||
FileAbstractImage* abstractImage();
|
// function is called for each frame to be saved for sequence-like
|
||||||
|
// files, or just once to encode animation formats.
|
||||||
|
// The file format needs the FILE_ENCODE_ABSTRACT_IMAGE flag to
|
||||||
|
// use this.
|
||||||
|
FileAbstractImage* abstractImageToSave();
|
||||||
void setOnTheFlyScale(const gfx::PointF& scale);
|
void setOnTheFlyScale(const gfx::PointF& scale);
|
||||||
|
|
||||||
const std::string& error() const { return m_error; }
|
const std::string& error() const { return m_error; }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -206,7 +206,7 @@ static int get_time_precision(const FileAbstractImage* sprite,
|
||||||
|
|
||||||
bool FliFormat::onSave(FileOp* fop)
|
bool FliFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const FileAbstractImage* sprite = fop->abstractImage();
|
const FileAbstractImage* sprite = fop->abstractImageToSave();
|
||||||
|
|
||||||
// Open the file to write in binary mode
|
// Open the file to write in binary mode
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
|
@ -251,7 +251,7 @@ bool FliFormat::onSave(FileOp* fop)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the frame in the bitmap
|
// Render the frame in the bitmap
|
||||||
sprite->renderFrame(frame, bmp.get());
|
sprite->renderFrame(frame, fop->roi().frameBounds(frame), bmp.get());
|
||||||
|
|
||||||
// How many times this frame should be written to get the same
|
// How many times this frame should be written to get the same
|
||||||
// time that it has in the sprite
|
// time that it has in the sprite
|
||||||
|
|
|
@ -970,7 +970,7 @@ public:
|
||||||
: m_fop(fop)
|
: m_fop(fop)
|
||||||
, m_gifFile(gifFile)
|
, m_gifFile(gifFile)
|
||||||
, m_sprite(fop->document()->sprite())
|
, m_sprite(fop->document()->sprite())
|
||||||
, m_img(fop->abstractImage())
|
, m_img(fop->abstractImageToSave())
|
||||||
, m_spec(m_img->spec())
|
, m_spec(m_img->spec())
|
||||||
, m_spriteBounds(m_spec.bounds())
|
, m_spriteBounds(m_spec.bounds())
|
||||||
, m_hasBackground(m_img->isOpaque())
|
, m_hasBackground(m_img->isOpaque())
|
||||||
|
@ -1570,7 +1570,7 @@ private:
|
||||||
clear_image(dst, m_bgIndex);
|
clear_image(dst, m_bgIndex);
|
||||||
else
|
else
|
||||||
clear_image(dst, 0);
|
clear_image(dst, 0);
|
||||||
m_img->renderFrame(frame, dst);
|
m_img->renderFrame(frame, m_fop->roi().frameBounds(frame), dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -178,7 +178,7 @@ bool JpegFormat::onLoad(FileOp* fop)
|
||||||
jpeg_start_decompress(&dinfo);
|
jpeg_start_decompress(&dinfo);
|
||||||
|
|
||||||
// Create the image.
|
// Create the image.
|
||||||
ImageRef image = fop->sequenceImage(
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
|
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
|
||||||
IMAGE_GRAYSCALE),
|
IMAGE_GRAYSCALE),
|
||||||
dinfo.output_width,
|
dinfo.output_width,
|
||||||
|
@ -353,7 +353,7 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
struct jpeg_compress_struct cinfo;
|
struct jpeg_compress_struct cinfo;
|
||||||
struct error_mgr jerr;
|
struct error_mgr jerr;
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
const ImageSpec spec = img->spec();
|
const ImageSpec spec = img->spec();
|
||||||
JSAMPARRAY buffer;
|
JSAMPARRAY buffer;
|
||||||
JDIMENSION buffer_height;
|
JDIMENSION buffer_height;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2022 Igara Studio S.A.
|
// Copyright (C) 2022-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -106,10 +106,10 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||||
for (c=0; c<60; c++) /* skip some more junk */
|
for (c=0; c<60; c++) /* skip some more junk */
|
||||||
fgetc(f);
|
fgetc(f);
|
||||||
|
|
||||||
ImageRef image = fop->sequenceImage(bpp == 8 ?
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
IMAGE_INDEXED:
|
(bpp == 8 ? IMAGE_INDEXED:
|
||||||
IMAGE_RGB,
|
IMAGE_RGB),
|
||||||
width, height);
|
width, height);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||||
#ifdef ENABLE_SAVE
|
#ifdef ENABLE_SAVE
|
||||||
bool PcxFormat::onSave(FileOp* fop)
|
bool PcxFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
const ImageSpec spec = img->spec();
|
const ImageSpec spec = img->spec();
|
||||||
int c, r, g, b;
|
int c, r, g, b;
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
|
@ -278,7 +278,8 @@ bool PngFormat::onLoad(FileOp* fop)
|
||||||
|
|
||||||
int imageWidth = png_get_image_width(png, info);
|
int imageWidth = png_get_image_width(png, info);
|
||||||
int imageHeight = png_get_image_height(png, info);
|
int imageHeight = png_get_image_height(png, info);
|
||||||
ImageRef image = fop->sequenceImage(pixelFormat, imageWidth, imageHeight);
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
|
pixelFormat, imageWidth, imageHeight);
|
||||||
if (!image)
|
if (!image)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -551,7 +552,7 @@ bool PngFormat::onSave(FileOp* fop)
|
||||||
|
|
||||||
png_init_io(png, fp);
|
png_init_io(png, fp);
|
||||||
|
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
const ImageSpec spec = img->spec();
|
const ImageSpec spec = img->spec();
|
||||||
|
|
||||||
switch (spec.colorMode()) {
|
switch (spec.colorMode()) {
|
||||||
|
|
|
@ -76,9 +76,10 @@ bool QoiFormat::onLoad(FileOp* fop)
|
||||||
if (!pixels)
|
if (!pixels)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ImageRef image = fop->sequenceImage(IMAGE_RGB,
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
desc.width,
|
IMAGE_RGB,
|
||||||
desc.height);
|
desc.width,
|
||||||
|
desc.height);
|
||||||
if (!image)
|
if (!image)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -136,7 +137,7 @@ bool QoiFormat::onLoad(FileOp* fop)
|
||||||
|
|
||||||
bool QoiFormat::onSave(FileOp* fop)
|
bool QoiFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
FILE* f = handle.get();
|
FILE* f = handle.get();
|
||||||
doc::ImageRef image = img->getScaledImage();
|
doc::ImageRef image = img->getScaledImage();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (c) 2018-2022 Igara Studio S.A.
|
// Copyright (c) 2018-2023 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// the End-User License Agreement for Aseprite.
|
||||||
|
@ -81,7 +81,7 @@ bool SvgFormat::onLoad(FileOp* fop)
|
||||||
|
|
||||||
bool SvgFormat::onSave(FileOp* fop)
|
bool SvgFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const ImageRef image = fop->sequenceImage();
|
const ImageRef image = fop->sequenceImageToSave();
|
||||||
int x, y, c, r, g, b, a, alpha;
|
int x, y, c, r, g, b, a, alpha;
|
||||||
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
||||||
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
|
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -165,9 +165,10 @@ bool TgaFormat::onLoad(FileOp* fop)
|
||||||
if (decoder.hasAlpha())
|
if (decoder.hasAlpha())
|
||||||
fop->sequenceSetHasAlpha(true);
|
fop->sequenceSetHasAlpha(true);
|
||||||
|
|
||||||
ImageRef image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
|
ImageRef image = fop->sequenceImageToLoad(
|
||||||
spec.width(),
|
(doc::PixelFormat)spec.colorMode(),
|
||||||
spec.height());
|
spec.width(),
|
||||||
|
spec.height());
|
||||||
if (!image)
|
if (!image)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ void prepare_header(tga::Header& header,
|
||||||
|
|
||||||
bool TgaFormat::onSave(FileOp* fop)
|
bool TgaFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const FileAbstractImage* img = fop->abstractImage();
|
const FileAbstractImage* img = fop->abstractImageToSave();
|
||||||
const Palette* palette = fop->sequenceGetPalette();
|
const Palette* palette = fop->sequenceGetPalette();
|
||||||
|
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
|
|
|
@ -257,7 +257,7 @@ bool WebPFormat::onSave(FileOp* fop)
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
FILE* fp = handle.get();
|
FILE* fp = handle.get();
|
||||||
|
|
||||||
const FileAbstractImage* sprite = fop->abstractImage();
|
const FileAbstractImage* sprite = fop->abstractImageToSave();
|
||||||
const int w = sprite->width();
|
const int w = sprite->width();
|
||||||
const int h = sprite->height();
|
const int h = sprite->height();
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ bool WebPFormat::onSave(FileOp* fop)
|
||||||
for (frame_t frame : fop->roi().selectedFrames()) {
|
for (frame_t frame : fop->roi().selectedFrames()) {
|
||||||
// Render the frame in the bitmap
|
// Render the frame in the bitmap
|
||||||
clear_image(image.get(), image->maskColor());
|
clear_image(image.get(), image->maskColor());
|
||||||
sprite->renderFrame(frame, image.get());
|
sprite->renderFrame(frame, fop->roi().frameBounds(frame), image.get());
|
||||||
|
|
||||||
// Switch R <-> B channels because WebPAnimEncoderAssemble()
|
// Switch R <-> B channels because WebPAnimEncoderAssemble()
|
||||||
// expects MODE_BGRA pictures.
|
// expects MODE_BGRA pictures.
|
||||||
|
|
|
@ -178,7 +178,7 @@ cd $oldwd
|
||||||
|
|
||||||
if [[ "$(uname)" =~ "MINGW" ]] || [[ "$(uname)" =~ "MSYS" ]] ; then
|
if [[ "$(uname)" =~ "MINGW" ]] || [[ "$(uname)" =~ "MSYS" ]] ; then
|
||||||
# Ignore this test on Windows because we cannot give * as a parameter (?)
|
# Ignore this test on Windows because we cannot give * as a parameter (?)
|
||||||
echo Do nothing
|
echo Skip one -save-as test because Windows does not support using asterisk in arguments without listing files
|
||||||
else
|
else
|
||||||
d=$t/save-as-groups-and-hidden
|
d=$t/save-as-groups-and-hidden
|
||||||
$ASEPRITE -b sprites/groups2.aseprite -layer \* -save-as "$d/g2-all.png" || exit 1
|
$ASEPRITE -b sprites/groups2.aseprite -layer \* -save-as "$d/g2-all.png" || exit 1
|
||||||
|
@ -354,3 +354,56 @@ for f = 1,#b.frames do
|
||||||
end
|
end
|
||||||
EOF
|
EOF
|
||||||
$ASEPRITE -b -script "$d/compare.lua" || exit 1
|
$ASEPRITE -b -script "$d/compare.lua" || exit 1
|
||||||
|
|
||||||
|
# Test -save-as selection to gif
|
||||||
|
# https://github.com/aseprite/aseprite/issues/3827
|
||||||
|
d=$t/save-selection-to-gif
|
||||||
|
mkdir $d
|
||||||
|
cat >$d/save.lua <<EOF
|
||||||
|
local a = app.open("sprites/tags3.aseprite")
|
||||||
|
assert(a.width == 4)
|
||||||
|
assert(a.height == 4)
|
||||||
|
app.command.SaveFileCopyAs{
|
||||||
|
filename="$d/output.gif",
|
||||||
|
bounds=Rectangle(1, 2, 3, 2)
|
||||||
|
}
|
||||||
|
local b = app.open("$d/output.gif")
|
||||||
|
assert(b.width == 3)
|
||||||
|
assert(b.height == 2)
|
||||||
|
EOF
|
||||||
|
"$ASEPRITE" -b -script "$d/save.lua" || exit 1
|
||||||
|
|
||||||
|
# Saving moving slice
|
||||||
|
d=$t/save-moving-slice
|
||||||
|
$ASEPRITE -b sprites/slices-moving.aseprite -slice square -save-as $d/output.gif || exit 1
|
||||||
|
$ASEPRITE -b sprites/slices-moving.aseprite -slice square -save-as $d/output.png || exit 1
|
||||||
|
$ASEPRITE -b sprites/slices-moving.aseprite -scale 2 -slice square -save-as $d/scaled.gif || exit 1
|
||||||
|
$ASEPRITE -b sprites/slices-moving.aseprite -scale 2 -slice square -save-as $d/scaled.png || exit 1
|
||||||
|
cat >$d/compare.lua <<EOF
|
||||||
|
local a = app.open("$d/output.gif")
|
||||||
|
local b = app.open("$d/output1.png")
|
||||||
|
app.command.OpenFile{ filename="$d/output1.png", oneframe=1 } local b1 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/output2.png", oneframe=1 } local b2 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/output3.png", oneframe=1 } local b3 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/output4.png", oneframe=1 } local b4 = app.sprite
|
||||||
|
assert(a.bounds == Rectangle(0, 0, 4, 2))
|
||||||
|
assert(b.bounds == Rectangle(0, 0, 4, 2))
|
||||||
|
assert(b1.bounds == Rectangle(0, 0, 2, 2))
|
||||||
|
assert(b2.bounds == Rectangle(0, 0, 4, 2))
|
||||||
|
assert(b3.bounds == Rectangle(0, 0, 3, 2))
|
||||||
|
assert(b4.bounds == Rectangle(0, 0, 4, 2))
|
||||||
|
|
||||||
|
local c = app.open("$d/scaled.gif")
|
||||||
|
local d = app.open("$d/scaled1.png")
|
||||||
|
app.command.OpenFile{ filename="$d/scaled1.png", oneframe=1 } local d1 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/scaled2.png", oneframe=1 } local d2 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/scaled3.png", oneframe=1 } local d3 = app.sprite
|
||||||
|
app.command.OpenFile{ filename="$d/scaled4.png", oneframe=1 } local d4 = app.sprite
|
||||||
|
assert(c.bounds == Rectangle(0, 0, 8, 4))
|
||||||
|
assert(d.bounds == Rectangle(0, 0, 8, 4))
|
||||||
|
assert(d1.bounds == Rectangle(0, 0, 4, 4))
|
||||||
|
assert(d2.bounds == Rectangle(0, 0, 8, 4))
|
||||||
|
assert(d3.bounds == Rectangle(0, 0, 6, 4))
|
||||||
|
assert(d4.bounds == Rectangle(0, 0, 8, 4))
|
||||||
|
EOF
|
||||||
|
$ASEPRITE -b -script "$d/compare.lua" || exit 1
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
-- Copyright (C) 2022 Igara Studio S.A.
|
-- Copyright (C) 2022-2023 Igara Studio S.A.
|
||||||
--
|
--
|
||||||
-- This file is released under the terms of the MIT license.
|
-- This file is released under the terms of the MIT license.
|
||||||
-- Read LICENSE.txt for more information.
|
-- Read LICENSE.txt for more information.
|
||||||
|
|
||||||
function fix_test_img(testImg, scale, fileExt, cm, c1)
|
function fix_images(testImg, scale, fileExt, c, cm, c1)
|
||||||
|
-- GIF file is loaded as indexed, so we have to convert from indexed
|
||||||
|
-- to the ColorMode
|
||||||
|
if c.colorMode ~= cm then
|
||||||
|
assert(fileExt == "gif" or fileExt == "bmp")
|
||||||
|
|
||||||
|
if cm == ColorMode.RGB then
|
||||||
|
app.sprite = c
|
||||||
|
app.command.ChangePixelFormat{ format="rgb" }
|
||||||
|
elseif cm == ColorMode.GRAYSCALE then
|
||||||
|
app.sprite = c
|
||||||
|
app.command.ChangePixelFormat{ format="grayscale" }
|
||||||
|
else
|
||||||
|
assert(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- With file formats that don't support alpha channel, we
|
-- With file formats that don't support alpha channel, we
|
||||||
-- compare totally transparent pixels (alpha=0) with black.
|
-- compare totally transparent pixels (alpha=0) with black.
|
||||||
if fileExt == "tga" and cm == ColorMode.GRAYSCALE then
|
if fileExt == "tga" and cm == ColorMode.GRAYSCALE then
|
||||||
|
@ -22,6 +38,16 @@ function fix_test_img(testImg, scale, fileExt, cm, c1)
|
||||||
testImg:resize(testImg.width*scale, testImg.height*scale)
|
testImg:resize(testImg.width*scale, testImg.height*scale)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function compatible_modes(fileExt, cm)
|
||||||
|
return
|
||||||
|
-- TODO support saving any color mode to FLI files on the fly
|
||||||
|
(fileExt ~= "fli" or cm == ColorMode.INDEXED) and
|
||||||
|
-- TODO Review grayscale support in bmp files
|
||||||
|
(fileExt ~= "bmp" or cm ~= ColorMode.GRAYSCALE) and
|
||||||
|
-- TODO Review grayscale/indexed support in webp files
|
||||||
|
(fileExt ~= "webp" or cm == ColorMode.RGB)
|
||||||
|
end
|
||||||
|
|
||||||
for _,cm in ipairs{ ColorMode.RGB,
|
for _,cm in ipairs{ ColorMode.RGB,
|
||||||
ColorMode.GRAYSCALE,
|
ColorMode.GRAYSCALE,
|
||||||
ColorMode.INDEXED } do
|
ColorMode.INDEXED } do
|
||||||
|
@ -59,44 +85,26 @@ for _,cm in ipairs{ ColorMode.RGB,
|
||||||
assert(spr.filename == "_test_b.png")
|
assert(spr.filename == "_test_b.png")
|
||||||
|
|
||||||
-- Scale
|
-- Scale
|
||||||
for _,fn in ipairs{ "_test_c_scaled.png",
|
for _,fn in ipairs{ "_test_c_scaled.bmp",
|
||||||
"_test_c_scaled.gif",
|
|
||||||
"_test_c_scaled.fli",
|
"_test_c_scaled.fli",
|
||||||
|
"_test_c_scaled.gif",
|
||||||
|
"_test_c_scaled.png",
|
||||||
"_test_c_scaled.tga",
|
"_test_c_scaled.tga",
|
||||||
"_test_c_scaled.bmp" } do
|
"_test_c_scaled.webp" } do
|
||||||
local fileExt = app.fs.fileExtension(fn)
|
local fileExt = app.fs.fileExtension(fn)
|
||||||
|
|
||||||
-- TODO support saving any color mode to FLI files on the fly
|
if compatible_modes(fileExt, cm) then
|
||||||
if (fileExt ~= "fli" or cm == ColorMode.INDEXED) and
|
for _,scale in ipairs({ 0.25, 0.5, 1, 2, 3, 4 }) do
|
||||||
-- TODO Review grayscale support in bmp files
|
|
||||||
(fileExt ~= "bmp" or cm ~= ColorMode.GRAYSCALE) then
|
|
||||||
for _,scale in ipairs({ 1, 2, 3, 4 }) do
|
|
||||||
print(fn, scale, cm)
|
print(fn, scale, cm)
|
||||||
|
|
||||||
app.activeSprite = spr
|
app.sprite = spr
|
||||||
app.command.SaveFileCopyAs{ filename=fn, scale=scale }
|
app.command.SaveFileCopyAs{ filename=fn, scale=scale }
|
||||||
local c = app.open(fn)
|
local c = app.open(fn)
|
||||||
assert(c.width == spr.width*scale)
|
assert(c.width == spr.width*scale)
|
||||||
assert(c.height == spr.height*scale)
|
assert(c.height == spr.height*scale)
|
||||||
|
|
||||||
-- GIF file is loaded as indexed, so we have to convert from
|
|
||||||
-- indexed to the ColorMode
|
|
||||||
if c.colorMode ~= cm then
|
|
||||||
assert(fileExt == "gif" or fileExt == "bmp")
|
|
||||||
|
|
||||||
if cm == ColorMode.RGB then
|
|
||||||
app.activeSprite = c
|
|
||||||
app.command.ChangePixelFormat{ format="rgb" }
|
|
||||||
elseif cm == ColorMode.GRAYSCALE then
|
|
||||||
app.activeSprite = c
|
|
||||||
app.command.ChangePixelFormat{ format="grayscale" }
|
|
||||||
else
|
|
||||||
assert(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local testImg = Image(spr.cels[1].image)
|
local testImg = Image(spr.cels[1].image)
|
||||||
fix_test_img(testImg, scale, fileExt, cm, c1)
|
fix_images(testImg, scale, fileExt, c, cm, c1)
|
||||||
if not c.cels[1].image:isEqual(testImg) then
|
if not c.cels[1].image:isEqual(testImg) then
|
||||||
c.cels[1].image:saveAs("_testA.png")
|
c.cels[1].image:saveAs("_testA.png")
|
||||||
testImg:saveAs("_testB.png")
|
testImg:saveAs("_testB.png")
|
||||||
|
@ -110,26 +118,26 @@ for _,cm in ipairs{ ColorMode.RGB,
|
||||||
-- Scale + Slices
|
-- Scale + Slices
|
||||||
local slice = spr:newSlice(Rectangle(1, 2, 8, 15))
|
local slice = spr:newSlice(Rectangle(1, 2, 8, 15))
|
||||||
slice.name = "small_slice"
|
slice.name = "small_slice"
|
||||||
for _,fn in ipairs({ "_test_c_small_slice.png",
|
for _,fn in ipairs({ "_test_c_small_slice.bmp",
|
||||||
-- TODO slices aren't supported in gif/fli yet
|
"_test_c_small_slice.fli",
|
||||||
--"_test_c_small_slice.gif",
|
"_test_c_small_slice.gif",
|
||||||
--"_test_c_small_slice.fli",
|
"_test_c_small_slice.png",
|
||||||
"_test_c_small_slice.tga",
|
"_test_c_small_slice.tga",
|
||||||
"_test_c_small_slice.bmp" }) do
|
"_test_c_small_slice.webp" }) do
|
||||||
local fileExt = app.fs.fileExtension(fn)
|
local fileExt = app.fs.fileExtension(fn)
|
||||||
|
|
||||||
if (fileExt ~= "bmp" or cm ~= ColorMode.GRAYSCALE) then
|
if compatible_modes(fileExt, cm) then
|
||||||
for _,scale in ipairs({ 1, 2, 3, 4 }) do
|
for _,scale in ipairs({ 0.25, 0.5, 1, 2, 3, 4 }) do
|
||||||
print(fn, scale, cm)
|
print(fn, scale, cm)
|
||||||
|
|
||||||
app.activeSprite = spr
|
app.sprite = spr
|
||||||
app.command.SaveFileCopyAs{ filename=fn, slice="small_slice", scale=scale }
|
app.command.SaveFileCopyAs{ filename=fn, slice="small_slice", scale=scale }
|
||||||
local c = app.open(fn)
|
local c = app.open(fn)
|
||||||
assert(c.width == slice.bounds.width*scale)
|
assert(c.width == slice.bounds.width*scale)
|
||||||
assert(c.height == slice.bounds.height*scale)
|
assert(c.height == slice.bounds.height*scale)
|
||||||
|
|
||||||
local testImg = Image(spr.cels[1].image, spr.slices[1].bounds)
|
local testImg = Image(spr.cels[1].image, spr.slices[1].bounds)
|
||||||
fix_test_img(testImg, scale, fileExt, cm, c1)
|
fix_images(testImg, scale, fileExt, c, cm, c1)
|
||||||
if not c.cels[1].image:isEqual(testImg) then
|
if not c.cels[1].image:isEqual(testImg) then
|
||||||
c.cels[1].image:saveAs("_testA.png")
|
c.cels[1].image:saveAs("_testA.png")
|
||||||
testImg:saveAs("_testB.png")
|
testImg:saveAs("_testB.png")
|
||||||
|
|
|
@ -28,3 +28,6 @@
|
||||||
* `file-tests-props.aseprite`: Indexed, 64x64, 6 frames, 4 layers (one
|
* `file-tests-props.aseprite`: Indexed, 64x64, 6 frames, 4 layers (one
|
||||||
of them is a tilemap), 13 cels, 1 tag.
|
of them is a tilemap), 13 cels, 1 tag.
|
||||||
* `slices.aseprite`: Indexed, 4x4, background layer, 2 slices.
|
* `slices.aseprite`: Indexed, 4x4, background layer, 2 slices.
|
||||||
|
* `slices-moving.aseprite`: Indexed, 4x4, 1 linked cel in 4 frames,
|
||||||
|
background layer, 1 slice with 4 keyframes (each keyframe with a
|
||||||
|
different position/size).
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue