New "on the fly" resize when saving/exporting image (fix #1112)

Implemented using a new FileAbstractImage interface to get scanlines
for each frame automatically resized (without modifying the original
sprite/without using SpriteSize command/adding new undo information).

Related to #3008
This commit is contained in:
David Capello 2022-06-14 23:19:39 -03:00
parent c58dae51fa
commit 4aa5fedfec
15 changed files with 403 additions and 147 deletions

View File

@ -189,7 +189,9 @@ void SaveFileBaseCommand::saveDocumentInBackground(
const Context* context, const Context* context,
Doc* document, Doc* document,
const std::string& filename, const std::string& filename,
const MarkAsSaved markAsSaved) const MarkAsSaved markAsSaved,
const ResizeOnTheFly resizeOnTheFly,
const gfx::PointF& scale)
{ {
if (params().aniDir.isSet()) { if (params().aniDir.isSet()) {
switch (params().aniDir()) { switch (params().aniDir()) {
@ -215,6 +217,9 @@ void SaveFileBaseCommand::saveDocumentInBackground(
if (!fop) if (!fop)
return; return;
if (resizeOnTheFly == ResizeOnTheFly::On)
fop->setOnTheFlyScale(scale);
SaveFileJob job(fop.get()); SaveFileJob job(fop.get());
job.showProgressWindow(); job.showProgressWindow();
@ -339,9 +344,8 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
std::string outputFilename = params().filename(); std::string outputFilename = params().filename();
std::string layers = kAllLayers; std::string layers = kAllLayers;
std::string frames = kAllFrames; std::string frames = kAllFrames;
double xscale = 1.0;
double yscale = 1.0;
bool applyPixelRatio = false; bool applyPixelRatio = false;
gfx::PointF scale(params().scale(), params().scale());
doc::AniDir aniDirValue = params().aniDir(); doc::AniDir aniDirValue = params().aniDir();
bool isForTwitter = false; bool isForTwitter = false;
@ -366,7 +370,17 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
return result; return result;
}); });
win.setAniDir(aniDirValue); if (params().filename.isSet()) {
std::string outputPath = base::get_file_path(outputFilename);
if (outputPath.empty()) {
outputPath = base::get_file_path(doc->filename());
outputFilename = base::join_path(outputPath, outputFilename);
}
win.setOutputFilename(outputFilename);
}
if (params().scale.isSet()) win.setResizeScale(scale);
if (params().aniDir.isSet()) win.setAniDir(aniDirValue);
win.remapWindow(); win.remapWindow();
load_window_pos(&win, "ExportFile"); load_window_pos(&win, "ExportFile");
@ -395,7 +409,7 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
layers = win.layersValue(); layers = win.layersValue();
frames = win.framesValue(); frames = win.framesValue();
xscale = yscale = win.resizeValue(); scale.x = scale.y = win.resizeValue();
applyPixelRatio = win.applyPixelRatio(); applyPixelRatio = win.applyPixelRatio();
aniDirValue = win.aniDirValue(); aniDirValue = win.aniDirValue();
isForTwitter = win.isForTwitter(); isForTwitter = win.isForTwitter();
@ -405,21 +419,23 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
// Pixel ratio // Pixel ratio
if (applyPixelRatio) { if (applyPixelRatio) {
doc::PixelRatio pr = doc->sprite()->pixelRatio(); doc::PixelRatio pr = doc->sprite()->pixelRatio();
xscale *= pr.w; scale.x *= pr.w;
yscale *= pr.h; scale.y *= pr.h;
} }
// Apply scale // First of all we'll try to use the "on the fly" scaling, to avoid
// using a resize command to apply the export scale.
const undo::UndoState* undoState = nullptr; const undo::UndoState* undoState = nullptr;
bool undoResize = false; bool undoResize = false;
if (xscale != 1.0 || yscale != 1.0) { const bool resizeOnTheFly = FileOp::checkIfFormatSupportResizeOnTheFly(outputFilename);
if (!resizeOnTheFly && (scale.x != 1.0 || scale.y != 1.0)) {
Command* resizeCmd = Commands::instance()->byId(CommandId::SpriteSize()); Command* resizeCmd = Commands::instance()->byId(CommandId::SpriteSize());
ASSERT(resizeCmd); ASSERT(resizeCmd);
if (resizeCmd) { if (resizeCmd) {
int width = doc->sprite()->width(); int width = doc->sprite()->width();
int height = doc->sprite()->height(); int height = doc->sprite()->height();
int newWidth = int(double(width) * xscale); int newWidth = int(double(width) * scale.x);
int newHeight = int(double(height) * yscale); int newHeight = int(double(height) * scale.y);
if (newWidth < 1) newWidth = 1; if (newWidth < 1) newWidth = 1;
if (newHeight < 1) newHeight = 1; if (newHeight < 1) newHeight = 1;
if (width != newWidth || height != newHeight) { if (width != newWidth || height != newHeight) {
@ -470,7 +486,10 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
saveDocumentInBackground( saveDocumentInBackground(
context, doc, outputFilename, context, doc, outputFilename,
MarkAsSaved::Off); MarkAsSaved::Off,
(resizeOnTheFly ? ResizeOnTheFly::On:
ResizeOnTheFly::Off),
scale);
} }
// Undo resize // Undo resize

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 Igara Studio S.A. // Copyright (C) 2021-2022 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
@ -13,6 +13,7 @@
#include "app/commands/new_params.h" #include "app/commands/new_params.h"
#include "doc/anidir.h" #include "doc/anidir.h"
#include "doc/selected_frames.h" #include "doc/selected_frames.h"
#include "gfx/point.h"
#include <string> #include <string>
@ -29,12 +30,14 @@ namespace app {
Param<doc::frame_t> fromFrame { this, 0, { "fromFrame", "from-frame" } }; Param<doc::frame_t> fromFrame { this, 0, { "fromFrame", "from-frame" } };
Param<doc::frame_t> toFrame { this, 0, { "toFrame", "to-frame" } }; Param<doc::frame_t> toFrame { this, 0, { "toFrame", "to-frame" } };
Param<bool> ignoreEmpty { this, false, "ignoreEmpty" }; Param<bool> ignoreEmpty { this, false, "ignoreEmpty" };
Param<double> scale { this, 1.0, "scale" };
}; };
class SaveFileBaseCommand : public CommandWithNewParams<SaveFileParams> { class SaveFileBaseCommand : public CommandWithNewParams<SaveFileParams> {
public: public:
enum class MarkAsSaved { Off, On }; enum class MarkAsSaved { Off, On };
enum class SaveInBackground { Off, On }; enum class SaveInBackground { Off, On };
enum class ResizeOnTheFly { Off, On };
SaveFileBaseCommand(const char* id, CommandFlags flags); SaveFileBaseCommand(const char* id, CommandFlags flags);
@ -53,7 +56,9 @@ namespace app {
const Context* context, const Context* context,
Doc* document, Doc* document,
const std::string& filename, const std::string& filename,
const MarkAsSaved markAsSaved); const MarkAsSaved markAsSaved,
const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off,
const gfx::PointF& scale = gfx::PointF(1.0, 1.0));
doc::SelectedFrames m_selFrames; doc::SelectedFrames m_selFrames;
bool m_adjustFramesByTag; bool m_adjustFramesByTag;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2019-2022 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
@ -67,7 +67,8 @@ class BmpFormat : public FileFormat {
FILE_SUPPORT_RGB | FILE_SUPPORT_RGB |
FILE_SUPPORT_GRAY | FILE_SUPPORT_GRAY |
FILE_SUPPORT_INDEXED | FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES; FILE_SUPPORT_SEQUENCES |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -688,7 +689,7 @@ bool BmpFormat::onLoad(FileOp *fop)
else else
rmask = gmask = bmask = 0; rmask = gmask = bmask = 0;
Image* image = fop->sequenceImage(pixelFormat, ImageRef image = fop->sequenceImage(pixelFormat,
infoheader.biWidth, infoheader.biWidth,
ABS((int)infoheader.biHeight)); ABS((int)infoheader.biHeight));
if (!image) { if (!image) {
@ -696,26 +697,26 @@ bool BmpFormat::onLoad(FileOp *fop)
} }
if (pixelFormat == IMAGE_RGB) if (pixelFormat == IMAGE_RGB)
clear_image(image, rgba(0, 0, 0, 255)); clear_image(image.get(), rgba(0, 0, 0, 255));
else else
clear_image(image, 0); clear_image(image.get(), 0);
switch (infoheader.biCompression) { switch (infoheader.biCompression) {
case BI_RGB: case BI_RGB:
read_image(f, image, &infoheader, fop); read_image(f, image.get(), &infoheader, fop);
break; break;
case BI_RLE8: case BI_RLE8:
read_rle8_compressed_image(f, image, &infoheader); read_rle8_compressed_image(f, image.get(), &infoheader);
break; break;
case BI_RLE4: case BI_RLE4:
read_rle4_compressed_image(f, image, &infoheader); read_rle4_compressed_image(f, image.get(), &infoheader);
break; break;
case BI_BITFIELDS: case BI_BITFIELDS:
if (read_bitfields_image(f, image, &infoheader, rmask, gmask, bmask) < 0) { if (read_bitfields_image(f, image.get(), &infoheader, rmask, gmask, bmask) < 0) {
fop->setError("Unsupported bitfields in the BMP file.\n"); fop->setError("Unsupported bitfields in the BMP file.\n");
return false; return false;
} }
@ -751,21 +752,22 @@ bool BmpFormat::onLoad(FileOp *fop)
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool BmpFormat::onSave(FileOp *fop) bool BmpFormat::onSave(FileOp *fop)
{ {
const Image* image = fop->sequenceImage(); const FileAbstractImage* img = fop->abstractImage();
const int w = image->width(); const ImageSpec spec = img->spec();
const int h = image->height(); const int w = spec.width();
const int h = spec.height();
int bfSize; int bfSize;
int biSizeImage; int biSizeImage;
int ncolors = fop->sequenceGetNColors(); int ncolors = fop->sequenceGetNColors();
int bpp = 0; int bpp = 0;
switch (image->pixelFormat()) { switch (spec.colorMode()) {
case IMAGE_RGB: case ColorMode::RGB:
bpp = 24; bpp = 24;
break; break;
case IMAGE_GRAYSCALE: case ColorMode::GRAYSCALE:
bpp = 8; bpp = 8;
break; break;
case IMAGE_INDEXED: { case ColorMode::INDEXED: {
if (ncolors > 16) if (ncolors > 16)
bpp = 8; bpp = 8;
else if (ncolors > 2) else if (ncolors > 2)
@ -776,7 +778,7 @@ bool BmpFormat::onSave(FileOp *fop)
break; break;
} }
default: default:
// TODO save IMAGE_BITMAP as 1bpp bmp? // TODO save ColorMode::BITMAP as 1bpp bmp?
// Invalid image format // Invalid image format
fop->setError("Unsupported color mode.\n"); fop->setError("Unsupported color mode.\n");
return false; return false;
@ -851,32 +853,38 @@ bool BmpFormat::onSave(FileOp *fop)
// Save image pixels (from bottom to top) // Save image pixels (from bottom to top)
for (i=h-1; i>=0; i--) { for (i=h-1; i>=0; i--) {
switch (image->pixelFormat()) { switch (spec.colorMode()) {
case IMAGE_RGB: case ColorMode::RGB: {
auto scanline = (const uint32_t*)img->getScanline(i);
for (j=0; j<w; ++j) { for (j=0; j<w; ++j) {
c = get_pixel_fast<RgbTraits>(image, j, i); c = scanline[j];
fputc(rgba_getb(c), f); fputc(rgba_getb(c), f);
fputc(rgba_getg(c), f); fputc(rgba_getg(c), f);
fputc(rgba_getr(c), f); fputc(rgba_getr(c), f);
} }
break; break;
case IMAGE_GRAYSCALE: }
case ColorMode::GRAYSCALE: {
auto scanline = (const uint16_t*)img->getScanline(i);
for (j=0; j<w; ++j) { for (j=0; j<w; ++j) {
c = get_pixel_fast<GrayscaleTraits>(image, j, i); c = scanline[j];
fputc(graya_getv(c), f); fputc(graya_getv(c), f);
} }
break; break;
case IMAGE_INDEXED: }
case ColorMode::INDEXED: {
auto scanline = (const uint8_t*)img->getScanline(i);
for (j=0; j<w; ) { for (j=0; j<w; ) {
uint8_t value = 0; uint8_t value = 0;
for (int k=colorsPerByte-1; k>=0 && j<w; --k, ++j) { for (int k=colorsPerByte-1; k>=0 && j<w; --k, ++j) {
c = get_pixel_fast<IndexedTraits>(image, j, i); c = scanline[j];
value |= (c & colorMask) << (bpp*k); value |= (c & colorMask) << (bpp*k);
} }
fputc(value, f); fputc(value, f);
} }
break; break;
} }
}
for (j=0; j<filler; j++) for (j=0; j<filler; j++)
fputc(0, f); fputc(0, f);

View File

@ -88,7 +88,7 @@ bool CssFormat::onLoad(FileOp* fop)
bool CssFormat::onSave(FileOp* fop) bool CssFormat::onSave(FileOp* fop)
{ {
const Image* image = fop->sequenceImage(); const ImageRef image = fop->sequenceImage();
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"));
@ -160,7 +160,7 @@ bool CssFormat::onSave(FileOp* fop)
case IMAGE_RGB: { case IMAGE_RGB: {
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<RgbTraits>(image, x, y); c = get_pixel_fast<RgbTraits>(image.get(), x, y);
alpha = rgba_geta(c); alpha = rgba_geta(c);
if (alpha != 0x00) { if (alpha != 0x00) {
print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c),
@ -175,7 +175,7 @@ bool CssFormat::onSave(FileOp* fop)
case IMAGE_GRAYSCALE: { case IMAGE_GRAYSCALE: {
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<GrayscaleTraits>(image, x, y); c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y);
auto v = graya_getv(c); auto v = graya_getv(c);
alpha = graya_geta(c); alpha = graya_geta(c);
if (alpha != 0x00) { if (alpha != 0x00) {
@ -204,7 +204,7 @@ bool CssFormat::onSave(FileOp* fop)
} }
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<IndexedTraits>(image, x, y); c = get_pixel_fast<IndexedTraits>(image.get(), x, y);
if (c != mask_color) { if (c != mask_color) {
if (css_options->withVars) { if (css_options->withVars) {
print_shadow_index(x, y, c, num_printed_pixels>0); print_shadow_index(x, y, c, num_printed_pixels>0);

View File

@ -34,6 +34,7 @@
#include "base/scoped_lock.h" #include "base/scoped_lock.h"
#include "base/string.h" #include "base/string.h"
#include "dio/detect_format.h" #include "dio/detect_format.h"
#include "doc/algorithm/resize_image.h"
#include "doc/doc.h" #include "doc/doc.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "render/quantization.h" #include "render/quantization.h"
@ -53,6 +54,130 @@ namespace app {
using namespace base; using namespace base;
class FileOp::FileAbstractImageImpl : public FileAbstractImage {
public:
FileAbstractImageImpl(FileOp* fop)
: m_doc(fop->document())
, m_sprite(m_doc->sprite())
, m_spec(m_sprite->spec())
, m_newBlend(fop->newBlend()) {
ASSERT(m_doc && m_sprite);
}
void setSliceBounds(const gfx::Rect& sliceBounds) {
m_spec.setWidth(sliceBounds.w * m_scale.x);
m_spec.setHeight(sliceBounds.h * m_scale.y);
}
void setUnscaledImage(const doc::frame_t frame,
const doc::ImageRef& image) {
if (m_spec.width() == image->width() &&
m_spec.height() == image->height()) {
m_tmpScaledImage = image;
}
else {
if (!m_tmpScaledImage)
m_tmpScaledImage.reset(doc::Image::create(m_spec));
doc::algorithm::resize_image(
image.get(),
m_tmpScaledImage.get(),
doc::algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
palette(frame),
m_sprite->rgbMap(frame),
image->maskColor());
}
}
// FileAbstractImage impl
doc::ImageSpec spec() const override {
return m_spec;
}
os::ColorSpaceRef osColorSpace() const override {
return m_doc->osColorSpace();
}
bool needAlpha() const override {
return m_sprite->needAlpha();
}
bool isOpaque() const override {
return m_sprite->isOpaque();
}
int frames() const override {
return m_sprite->totalFrames();
}
int frameDuration(doc::frame_t frame) const override {
return m_sprite->frameDuration(frame);
}
const doc::Palette* palette(doc::frame_t frame) const override {
ASSERT(m_sprite);
return m_sprite->palette(frame);
}
doc::PalettesList palettes() const override {
ASSERT(m_sprite);
return m_sprite->getPalettes();
}
const doc::ImageRef getScaledImage() const override {
return m_tmpScaledImage;
}
const uint8_t* getScanline(int y) const override {
return m_tmpScaledImage->getPixelAddress(0, y);
}
void renderFrame(const doc::frame_t frame, doc::Image* dst) const override {
const bool needResize =
(dst->width() != m_sprite->width() ||
dst->height() != m_sprite->height());
if (needResize && !m_tmpUnscaledRender) {
auto spec = m_sprite->spec();
spec.setColorMode(dst->colorMode());
m_tmpUnscaledRender.reset(doc::Image::create(spec));
}
render::Render render;
render.setNewBlend(m_newBlend);
render.setBgType(render::BgType::NONE);
render.renderSprite(
(needResize ? m_tmpUnscaledRender.get(): dst),
m_sprite, frame);
if (needResize) {
doc::algorithm::resize_image(
m_tmpUnscaledRender.get(),
dst,
doc::algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
palette(frame),
m_sprite->rgbMap(frame),
m_tmpUnscaledRender->maskColor());
}
}
void setScale(const gfx::PointF& scale) {
m_scale = scale;
m_spec.setWidth(m_spec.width() * m_scale.x);
m_spec.setHeight(m_spec.height() * m_scale.y);
}
private:
const Doc* m_doc;
const doc::Sprite* m_sprite;
doc::ImageSpec m_spec;
bool m_newBlend;
doc::ImageRef m_tmpScaledImage = nullptr;
mutable doc::ImageRef m_tmpUnscaledRender = nullptr;
gfx::PointF m_scale = gfx::PointF(1.0, 1.0);
};
base::paths get_readable_extensions() base::paths get_readable_extensions()
{ {
base::paths paths; base::paths paths;
@ -586,6 +711,18 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
return fop.release(); return fop.release();
} }
// static
bool FileOp::checkIfFormatSupportResizeOnTheFly(const std::string& filename)
{
// Get the format through the extension of the filename
FileFormat* fileFormat =
FileFormatsManager::instance()->getFileFormat(
dio::detect_format_by_file_extension(filename));
return (fileFormat &&
fileFormat->support(FILE_ENCODE_ABSTRACT_IMAGE));
}
// Executes the file operation: loads or saves the sprite. // Executes the file operation: loads or saves the sprite.
// //
// It can be called from a different thread of the one used // It can be called from a different thread of the one used
@ -774,6 +911,9 @@ void FileOp::operate(IFileOpProgress* progress)
if (!key || key->isEmpty()) if (!key || key->isEmpty())
continue; // Skip frame because there is no slice key continue; // Skip frame because there is no slice key
if (m_abstractImage)
m_abstractImage->setSliceBounds(key->bounds());
m_seq.image.reset( m_seq.image.reset(
Image::create(sprite->pixelFormat(), Image::create(sprite->pixelFormat(),
key->bounds().w, key->bounds().w,
@ -1080,7 +1220,7 @@ void FileOp::sequenceGetAlpha(int index, int* a) const
*a = 0; *a = 0;
} }
Image* FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h) ImageRef FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
{ {
Sprite* sprite; Sprite* sprite;
@ -1118,7 +1258,33 @@ Image* FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
m_seq.image.reset(Image::create(pixelFormat, w, h)); m_seq.image.reset(Image::create(pixelFormat, w, h));
m_seq.last_cel = new Cel(m_seq.frame++, ImageRef(nullptr)); m_seq.last_cel = new Cel(m_seq.frame++, ImageRef(nullptr));
return m_seq.image.get(); return m_seq.image;
}
void FileOp::makeAbstractImage()
{
ASSERT(m_format->support(FILE_ENCODE_ABSTRACT_IMAGE));
if (!m_abstractImage)
m_abstractImage = std::make_unique<FileAbstractImageImpl>(this);
}
FileAbstractImage* FileOp::abstractImage()
{
ASSERT(m_format->support(FILE_ENCODE_ABSTRACT_IMAGE));
makeAbstractImage();
// Use sequenceImage() to fill the current image
if (m_format->support(FILE_SUPPORT_SEQUENCES))
m_abstractImage->setUnscaledImage(m_seq.frame, sequenceImage());
return m_abstractImage.get();
}
void FileOp::setOnTheFlyScale(const gfx::PointF& scale)
{
makeAbstractImage();
m_abstractImage->setScale(scale);
} }
void FileOp::setError(const char *format, ...) void FileOp::setError(const char *format, ...)

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 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
@ -20,6 +20,7 @@
#include "doc/image_ref.h" #include "doc/image_ref.h"
#include "doc/pixel_format.h" #include "doc/pixel_format.h"
#include "doc/selected_frames.h" #include "doc/selected_frames.h"
#include "os/color_space.h"
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
@ -94,6 +95,33 @@ namespace app {
doc::SelectedFrames m_selFrames; doc::SelectedFrames m_selFrames;
}; };
// Used by file formats with FILE_ENCODE_ABSTRACT_IMAGE flag, to
// encode a sprite with an intermediate transformation on-the-fly
// (e.g. resizing).
class FileAbstractImage {
public:
virtual ~FileAbstractImage() { }
virtual doc::ImageSpec spec() const = 0;
virtual os::ColorSpaceRef osColorSpace() const = 0;
virtual bool needAlpha() const = 0;
virtual bool isOpaque() const = 0;
virtual int frames() const = 0;
virtual int frameDuration(doc::frame_t frame) const = 0;
virtual const doc::Palette* palette(doc::frame_t frame) const = 0;
virtual doc::PalettesList palettes() const = 0;
virtual const doc::ImageRef getScaledImage() const = 0;
// In case the file format can encode scanline by scanline
// (e.g. PNG format).
virtual const uint8_t* getScanline(int y) const = 0;
// In case that the encoder needs full frame renders (or compare
// between frames), e.g. GIF format.
virtual void renderFrame(const doc::frame_t frame, doc::Image* dst) const = 0;
};
// Structure to load & save files. // Structure to load & save files.
// //
// TODO This class do to many things. There should be a previous // TODO This class do to many things. There should be a previous
@ -113,6 +141,8 @@ namespace app {
const std::string& filenameFormat, const std::string& filenameFormat,
const bool ignoreEmptyFrames); const bool ignoreEmptyFrames);
static bool checkIfFormatSupportResizeOnTheFly(const std::string& filename);
~FileOp(); ~FileOp();
bool isSequence() const { return !m_seq.filename_list.empty(); } bool isSequence() const { return !m_seq.filename_list.empty(); }
@ -189,8 +219,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;
Image* sequenceImage(PixelFormat pixelFormat, int w, int h); ImageRef sequenceImage(PixelFormat pixelFormat, int w, int h);
const Image* sequenceImage() const { return m_seq.image.get(); } const ImageRef sequenceImage() 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;
@ -202,6 +232,11 @@ namespace app {
return m_seq.flags; return m_seq.flags;
} }
// Can be used to encode sequences/static files (e.g. png files)
// or animations (e.g. gif) resizing the result on the fly.
FileAbstractImage* abstractImage();
void setOnTheFlyScale(const gfx::PointF& scale);
const std::string& error() const { return m_error; } const std::string& error() const { return m_error; }
void setError(const char *error, ...); void setError(const char *error, ...);
bool hasError() const { return !m_error.empty(); } bool hasError() const { return !m_error.empty(); }
@ -276,7 +311,11 @@ namespace app {
int flags; int flags;
} m_seq; } m_seq;
class FileAbstractImageImpl;
std::unique_ptr<FileAbstractImageImpl> m_abstractImage;
void prepareForSequence(); void prepareForSequence();
void makeAbstractImage();
}; };
// Available extensions for each load/save operation. // Available extensions for each load/save operation.

View File

@ -30,6 +30,7 @@
#define FILE_SUPPORT_TAGS 0x00001000 #define FILE_SUPPORT_TAGS 0x00001000
#define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors #define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors
#define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000 #define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000
#define FILE_ENCODE_ABSTRACT_IMAGE 0x00008000 // Use the new FileAbstractImage
namespace app { namespace app {

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A. // Copyright (C) 2018-2022 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
@ -92,7 +92,8 @@ class GifFormat : public FileFormat {
FILE_SUPPORT_INDEXED | FILE_SUPPORT_INDEXED |
FILE_SUPPORT_FRAMES | FILE_SUPPORT_FRAMES |
FILE_SUPPORT_PALETTES | FILE_SUPPORT_PALETTES |
FILE_SUPPORT_GET_FORMAT_OPTIONS; FILE_SUPPORT_GET_FORMAT_OPTIONS |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -899,14 +900,15 @@ public:
GifEncoder(FileOp* fop, GifFileType* gifFile) GifEncoder(FileOp* fop, GifFileType* gifFile)
: m_fop(fop) : m_fop(fop)
, m_gifFile(gifFile) , m_gifFile(gifFile)
, m_document(fop->document())
, m_sprite(fop->document()->sprite()) , m_sprite(fop->document()->sprite())
, m_spriteBounds(m_sprite->bounds()) , m_img(fop->abstractImage())
, m_hasBackground(m_sprite->backgroundLayer() ? true: false) , m_spec(m_img->spec())
, m_spriteBounds(m_spec.bounds())
, m_hasBackground(m_img->isOpaque())
, m_bitsPerPixel(1) , m_bitsPerPixel(1)
, m_globalColormap(nullptr) , m_globalColormap(nullptr)
, m_quantizeColormaps(false) { , m_quantizeColormaps(false) {
if (m_sprite->pixelFormat() == IMAGE_INDEXED) { if (m_spec.colorMode() == ColorMode::INDEXED) {
for (Palette* palette : m_sprite->getPalettes()) { for (Palette* palette : m_sprite->getPalettes()) {
int bpp = GifBitSizeLimited(palette->size()); int bpp = GifBitSizeLimited(palette->size());
m_bitsPerPixel = std::max(m_bitsPerPixel, bpp); m_bitsPerPixel = std::max(m_bitsPerPixel, bpp);
@ -916,8 +918,8 @@ public:
m_bitsPerPixel = 8; m_bitsPerPixel = 8;
} }
if (m_sprite->pixelFormat() == IMAGE_INDEXED && if (m_spec.colorMode() == ColorMode::INDEXED &&
m_sprite->getPalettes().size() == 1) { m_img->palettes().size() == 1) {
// If some layer has opacity < 255 or a different blend mode, we // If some layer has opacity < 255 or a different blend mode, we
// need to create color palettes. // need to create color palettes.
for (const Layer* layer : m_sprite->allVisibleLayers()) { for (const Layer* layer : m_sprite->allVisibleLayers()) {
@ -933,7 +935,7 @@ public:
if (!m_quantizeColormaps) { if (!m_quantizeColormaps) {
m_globalColormap = createColorMap(m_sprite->palette(0)); m_globalColormap = createColorMap(m_sprite->palette(0));
m_bgIndex = m_sprite->transparentColor(); m_bgIndex = m_spec.maskColor();
} }
else else
m_bgIndex = 0; m_bgIndex = 0;
@ -1089,7 +1091,7 @@ private:
const DisposalMethod disposalMethod, const DisposalMethod disposalMethod,
const bool fixDuration) { const bool fixDuration) {
unsigned char extension_bytes[5]; unsigned char extension_bytes[5];
int frameDelay = m_sprite->frameDuration(frame) / 10; int frameDelay = m_img->frameDuration(frame) / 10;
// Fix duration for Twitter. It looks like the last frame must be // Fix duration for Twitter. It looks like the last frame must be
// 1/4 of its duration for some strange reason in the Twitter // 1/4 of its duration for some strange reason in the Twitter
@ -1356,12 +1358,8 @@ private:
} }
void renderFrame(frame_t frame, Image* dst) { void renderFrame(frame_t frame, Image* dst) {
render::Render render;
render.setNewBlend(m_fop->newBlend());
render.setBgType(render::BgType::NONE);
clear_image(dst, m_clearColor); clear_image(dst, m_clearColor);
render.renderSprite(dst, m_sprite, frame); m_img->renderFrame(frame, dst);
} }
private: private:
@ -1371,8 +1369,7 @@ private:
ColorMapObject* colormap = GifMakeMapObject(n, nullptr); ColorMapObject* colormap = GifMakeMapObject(n, nullptr);
// Color space conversions // Color space conversions
ConvertCS convert = convert_from_custom_to_srgb( ConvertCS convert = convert_from_custom_to_srgb(m_img->osColorSpace());
m_document->osColorSpace());
for (int i=0; i<n; ++i) { for (int i=0; i<n; ++i) {
color_t color; color_t color;
@ -1393,8 +1390,9 @@ private:
FileOp* m_fop; FileOp* m_fop;
GifFileType* m_gifFile; GifFileType* m_gifFile;
const Doc* m_document;
const Sprite* m_sprite; const Sprite* m_sprite;
const FileAbstractImage* m_img;
const ImageSpec m_spec;
gfx::Rect m_spriteBounds; gfx::Rect m_spriteBounds;
bool m_hasBackground; bool m_hasBackground;
int m_bgIndex; int m_bgIndex;

View File

@ -64,7 +64,8 @@ class JpegFormat : public FileFormat {
FILE_SUPPORT_RGB | FILE_SUPPORT_RGB |
FILE_SUPPORT_GRAY | FILE_SUPPORT_GRAY |
FILE_SUPPORT_SEQUENCES | FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_GET_FORMAT_OPTIONS; FILE_SUPPORT_GET_FORMAT_OPTIONS |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -177,7 +178,7 @@ bool JpegFormat::onLoad(FileOp* fop)
jpeg_start_decompress(&dinfo); jpeg_start_decompress(&dinfo);
// Create the image. // Create the image.
Image* image = fop->sequenceImage( ImageRef image = fop->sequenceImage(
(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,
@ -352,7 +353,8 @@ bool JpegFormat::onSave(FileOp* fop)
{ {
struct jpeg_compress_struct cinfo; struct jpeg_compress_struct cinfo;
struct error_mgr jerr; struct error_mgr jerr;
const Image* image = fop->sequenceImage(); const FileAbstractImage* img = fop->abstractImage();
const ImageSpec spec = img->spec();
JSAMPARRAY buffer; JSAMPARRAY buffer;
JDIMENSION buffer_height; JDIMENSION buffer_height;
const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions()); const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
@ -377,10 +379,10 @@ bool JpegFormat::onSave(FileOp* fop)
jpeg_stdio_dest(&cinfo, file); jpeg_stdio_dest(&cinfo, file);
// SET parameters for compression. // SET parameters for compression.
cinfo.image_width = image->width(); cinfo.image_width = spec.width();
cinfo.image_height = image->height(); cinfo.image_height = spec.height();
if (image->pixelFormat() == IMAGE_GRAYSCALE) { if (spec.colorMode() == ColorMode::GRAYSCALE) {
cinfo.input_components = 1; cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE; cinfo.in_color_space = JCS_GRAYSCALE;
} }
@ -427,15 +429,15 @@ bool JpegFormat::onSave(FileOp* fop)
// Write each scan line. // Write each scan line.
while (cinfo.next_scanline < cinfo.image_height) { while (cinfo.next_scanline < cinfo.image_height) {
// RGB // RGB
if (image->pixelFormat() == IMAGE_RGB) { if (spec.colorMode() == ColorMode::RGB) {
uint32_t* src_address; uint32_t* src_address;
uint8_t* dst_address; uint8_t* dst_address;
int x, y; int x, y;
for (y=0; y<(int)buffer_height; y++) { for (y=0; y<(int)buffer_height; y++) {
src_address = (uint32_t*)image->getPixelAddress(0, cinfo.next_scanline+y); src_address = (uint32_t*)img->getScanline(cinfo.next_scanline+y);
dst_address = ((uint8_t**)buffer)[y]; dst_address = ((uint8_t**)buffer)[y];
for (x=0; x<image->width(); ++x) { for (x=0; x<spec.width(); ++x) {
c = *(src_address++); c = *(src_address++);
*(dst_address++) = rgba_getr(c); *(dst_address++) = rgba_getr(c);
*(dst_address++) = rgba_getg(c); *(dst_address++) = rgba_getg(c);
@ -449,9 +451,9 @@ bool JpegFormat::onSave(FileOp* fop)
uint8_t* dst_address; uint8_t* dst_address;
int x, y; int x, y;
for (y=0; y<(int)buffer_height; y++) { for (y=0; y<(int)buffer_height; y++) {
src_address = (uint16_t*)image->getPixelAddress(0, cinfo.next_scanline+y); src_address = (uint16_t*)img->getScanline(cinfo.next_scanline+y);
dst_address = ((uint8_t**)buffer)[y]; dst_address = ((uint8_t**)buffer)[y];
for (x=0; x<image->width(); ++x) for (x=0; x<spec.width(); ++x)
*(dst_address++) = graya_getv(*(src_address++)); *(dst_address++) = graya_getv(*(src_address++));
} }
} }

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2022 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
@ -43,7 +44,8 @@ class PcxFormat : public FileFormat {
FILE_SUPPORT_RGB | FILE_SUPPORT_RGB |
FILE_SUPPORT_GRAY | FILE_SUPPORT_GRAY |
FILE_SUPPORT_INDEXED | FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES; FILE_SUPPORT_SEQUENCES |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -104,7 +106,7 @@ 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);
Image* image = fop->sequenceImage(bpp == 8 ? ImageRef image = fop->sequenceImage(bpp == 8 ?
IMAGE_INDEXED: IMAGE_INDEXED:
IMAGE_RGB, IMAGE_RGB,
width, height); width, height);
@ -113,7 +115,7 @@ bool PcxFormat::onLoad(FileOp* fop)
} }
if (bpp == 24) if (bpp == 24)
clear_image(image, rgba(0, 0, 0, 255)); clear_image(image.get(), rgba(0, 0, 0, 255));
for (y=0; y<height; y++) { /* read RLE encoded PCX data */ for (y=0; y<height; y++) { /* read RLE encoded PCX data */
x = xx = 0; x = xx = 0;
@ -131,7 +133,7 @@ bool PcxFormat::onLoad(FileOp* fop)
if (bpp == 8) { if (bpp == 8) {
while (c--) { while (c--) {
if (x < image->width()) if (x < image->width())
put_pixel_fast<IndexedTraits>(image, x, y, ch); put_pixel_fast<IndexedTraits>(image.get(), x, y, ch);
x++; x++;
} }
@ -139,8 +141,8 @@ bool PcxFormat::onLoad(FileOp* fop)
else { else {
while (c--) { while (c--) {
if (xx < image->width()) if (xx < image->width())
put_pixel_fast<RgbTraits>(image, xx, y, put_pixel_fast<RgbTraits>(image.get(), xx, y,
get_pixel_fast<RgbTraits>(image, xx, y) | ((ch & 0xff) << po)); get_pixel_fast<RgbTraits>(image.get(), xx, y) | ((ch & 0xff) << po));
x++; x++;
if (x == bytes_per_line) { if (x == bytes_per_line) {
@ -190,7 +192,8 @@ bool PcxFormat::onLoad(FileOp* fop)
#ifdef ENABLE_SAVE #ifdef ENABLE_SAVE
bool PcxFormat::onSave(FileOp* fop) bool PcxFormat::onSave(FileOp* fop)
{ {
const Image* image = fop->sequenceImage(); const FileAbstractImage* img = fop->abstractImage();
const ImageSpec spec = img->spec();
int c, r, g, b; int c, r, g, b;
int x, y; int x, y;
int runcount; int runcount;
@ -201,7 +204,7 @@ bool PcxFormat::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* f = handle.get(); FILE* f = handle.get();
if (image->pixelFormat() == IMAGE_RGB) { if (spec.colorMode() == ColorMode::RGB) {
depth = 24; depth = 24;
planes = 3; planes = 3;
} }
@ -216,8 +219,8 @@ bool PcxFormat::onSave(FileOp* fop)
fputc(8, f); /* 8 bits per pixel */ fputc(8, f); /* 8 bits per pixel */
fputw(0, f); /* xmin */ fputw(0, f); /* xmin */
fputw(0, f); /* ymin */ fputw(0, f); /* ymin */
fputw(image->width()-1, f); /* xmax */ fputw(spec.width()-1, f); /* xmax */
fputw(image->height()-1, f); /* ymax */ fputw(spec.height()-1, f); /* ymax */
fputw(320, f); /* HDpi */ fputw(320, f); /* HDpi */
fputw(200, f); /* VDpi */ fputw(200, f); /* VDpi */
@ -230,36 +233,39 @@ bool PcxFormat::onSave(FileOp* fop)
fputc(0, f); /* reserved */ fputc(0, f); /* reserved */
fputc(planes, f); /* one or three color planes */ fputc(planes, f); /* one or three color planes */
fputw(image->width(), f); /* number of bytes per scanline */ fputw(spec.width(), f); /* number of bytes per scanline */
fputw(1, f); /* color palette */ fputw(1, f); /* color palette */
fputw(image->width(), f); /* hscreen size */ fputw(spec.width(), f); /* hscreen size */
fputw(image->height(), f); /* vscreen size */ fputw(spec.height(), f); /* vscreen size */
for (c=0; c<54; c++) /* filler */ for (c=0; c<54; c++) /* filler */
fputc(0, f); fputc(0, f);
for (y=0; y<image->height(); y++) { /* for each scanline... */ for (y=0; y<spec.height(); y++) { /* for each scanline... */
runcount = 0; runcount = 0;
runchar = 0; runchar = 0;
for (x=0; x<image->width()*planes; x++) { /* for each pixel... */
const uint8_t* scanline = img->getScanline(y);
for (x=0; x<spec.width()*planes; x++) { /* for each pixel... */
if (depth == 8) { if (depth == 8) {
if (image->pixelFormat() == IMAGE_INDEXED) if (spec.colorMode() == ColorMode::INDEXED)
ch = get_pixel_fast<IndexedTraits>(image, x, y); ch = scanline[x];
else if (image->pixelFormat() == IMAGE_GRAYSCALE) { else if (spec.colorMode() == ColorMode::GRAYSCALE) {
c = get_pixel_fast<GrayscaleTraits>(image, x, y); c = ((const uint16_t*)scanline)[x];
ch = graya_getv(c); ch = graya_getv(c);
} }
} }
else { else {
if (x < image->width()) { if (x < spec.width()) {
c = get_pixel_fast<RgbTraits>(image, x, y); c = ((const uint32_t*)scanline)[x];
ch = rgba_getr(c); ch = rgba_getr(c);
} }
else if (x<image->width()*2) { else if (x<spec.width()*2) {
c = get_pixel_fast<RgbTraits>(image, x-image->width(), y); c = ((const uint32_t*)scanline)[x-spec.width()];
ch = rgba_getg(c); ch = rgba_getg(c);
} }
else { else {
c = get_pixel_fast<RgbTraits>(image, x-image->width()*2, y); c = ((const uint32_t*)scanline)[x-spec.width()*2];
ch = rgba_getb(c); ch = rgba_getb(c);
} }
} }
@ -285,7 +291,7 @@ bool PcxFormat::onSave(FileOp* fop)
fputc(runchar, f); fputc(runchar, f);
fop->setProgress((float)(y+1) / (float)(image->height())); fop->setProgress((float)(y+1) / (float)(spec.height()));
} }
if (depth == 8) { /* 256 color palette */ if (depth == 8) { /* 256 color palette */

View File

@ -54,7 +54,8 @@ class PngFormat : public FileFormat {
FILE_SUPPORT_GRAYA | FILE_SUPPORT_GRAYA |
FILE_SUPPORT_INDEXED | FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES | FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_PALETTE_WITH_ALPHA; FILE_SUPPORT_PALETTE_WITH_ALPHA |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -275,7 +276,7 @@ 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);
Image* image = fop->sequenceImage(pixelFormat, imageWidth, imageHeight); ImageRef image = fop->sequenceImage(pixelFormat, imageWidth, imageHeight);
if (!image) { if (!image) {
fop->setError("file_sequence_image %dx%d\n", imageWidth, imageHeight); fop->setError("file_sequence_image %dx%d\n", imageWidth, imageHeight);
return false; return false;
@ -550,23 +551,25 @@ bool PngFormat::onSave(FileOp* fop)
png_init_io(png, fp); png_init_io(png, fp);
const Image* image = fop->sequenceImage(); const FileAbstractImage* img = fop->abstractImage();
switch (image->pixelFormat()) { const ImageSpec spec = img->spec();
case IMAGE_RGB:
switch (spec.colorMode()) {
case ColorMode::RGB:
color_type = color_type =
(fop->document()->sprite()->needAlpha() || (img->needAlpha() ||
fix_one_alpha_pixel ? fix_one_alpha_pixel ?
PNG_COLOR_TYPE_RGB_ALPHA: PNG_COLOR_TYPE_RGB_ALPHA:
PNG_COLOR_TYPE_RGB); PNG_COLOR_TYPE_RGB);
break; break;
case IMAGE_GRAYSCALE: case ColorMode::GRAYSCALE:
color_type = color_type =
(fop->document()->sprite()->needAlpha() || (img->needAlpha() ||
fix_one_alpha_pixel ? fix_one_alpha_pixel ?
PNG_COLOR_TYPE_GRAY_ALPHA: PNG_COLOR_TYPE_GRAY_ALPHA:
PNG_COLOR_TYPE_GRAY); PNG_COLOR_TYPE_GRAY);
break; break;
case IMAGE_INDEXED: case ColorMode::INDEXED:
if (fix_one_alpha_pixel) if (fix_one_alpha_pixel)
color_type = PNG_COLOR_TYPE_RGB_ALPHA; color_type = PNG_COLOR_TYPE_RGB_ALPHA;
else else
@ -574,8 +577,8 @@ bool PngFormat::onSave(FileOp* fop)
break; break;
} }
const png_uint_32 width = image->width(); const png_uint_32 width = spec.width();
const png_uint_32 height = image->height(); const png_uint_32 height = spec.height();
png_set_IHDR(png, info, width, height, 8, color_type, png_set_IHDR(png, info, width, height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
@ -606,9 +609,9 @@ bool PngFormat::onSave(FileOp* fop)
png_set_unknown_chunks(png, info, &unknowns[0], num_unknowns); png_set_unknown_chunks(png, info, &unknowns[0], num_unknowns);
} }
if (fop->preserveColorProfile() && if (fop->preserveColorProfile() && spec.colorSpace()) {
fop->document()->sprite()->colorSpace()) saveColorSpace(png, info, spec.colorSpace().get());
saveColorSpace(png, info, fop->document()->sprite()->colorSpace().get()); }
if (color_type == PNG_COLOR_TYPE_PALETTE) { if (color_type == PNG_COLOR_TYPE_PALETTE) {
int c, r, g, b; int c, r, g, b;
@ -633,7 +636,7 @@ bool PngFormat::onSave(FileOp* fop)
// If the sprite does not have a (visible) background layer, we // If the sprite does not have a (visible) background layer, we
// put alpha=0 to the transparent color. // put alpha=0 to the transparent color.
int mask_entry = -1; int mask_entry = -1;
if (fop->document()->sprite()->backgroundLayer() == NULL || if (fop->document()->sprite()->backgroundLayer() == nullptr ||
!fop->document()->sprite()->backgroundLayer()->isVisible()) { !fop->document()->sprite()->backgroundLayer()->isVisible()) {
mask_entry = fop->document()->sprite()->transparentColor(); mask_entry = fop->document()->sprite()->transparentColor();
} }
@ -668,8 +671,8 @@ bool PngFormat::onSave(FileOp* fop)
unsigned int x, c, a; unsigned int x, c, a;
bool opaque = true; bool opaque = true;
if (image->pixelFormat() == IMAGE_RGB) { if (spec.colorMode() == ColorMode::RGB) {
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y); auto src_address = (const uint32_t*)img->getScanline(y);
for (x=0; x<width; ++x) { for (x=0; x<width; ++x) {
c = *(src_address++); c = *(src_address++);
@ -690,8 +693,8 @@ bool PngFormat::onSave(FileOp* fop)
} }
// In case that we are converting an indexed image to RGB just // In case that we are converting an indexed image to RGB just
// to convert one pixel with alpha=254. // to convert one pixel with alpha=254.
else if (image->pixelFormat() == IMAGE_INDEXED) { else if (spec.colorMode() == ColorMode::INDEXED) {
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y); auto src_address = (const uint8_t*)img->getScanline(y);
unsigned int x, c; unsigned int x, c;
int r, g, b, a; int r, g, b, a;
bool opaque = true; bool opaque = true;
@ -716,7 +719,7 @@ bool PngFormat::onSave(FileOp* fop)
} }
} }
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_RGB) { else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_RGB) {
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y); auto src_address = (const uint32_t*)img->getScanline(y);
unsigned int x, c; unsigned int x, c;
for (x=0; x<width; ++x) { for (x=0; x<width; ++x) {
@ -727,7 +730,7 @@ bool PngFormat::onSave(FileOp* fop)
} }
} }
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY_ALPHA) { else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY_ALPHA) {
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y); auto src_address = (const uint16_t*)img->getScanline(y);
unsigned int x, c, a; unsigned int x, c, a;
bool opaque = true; bool opaque = true;
@ -747,7 +750,7 @@ bool PngFormat::onSave(FileOp* fop)
} }
} }
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) { else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) {
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y); auto src_address = (const uint16_t*)img->getScanline(y);
unsigned int x, c; unsigned int x, c;
for (x=0; x<width; ++x) { for (x=0; x<width; ++x) {
@ -756,7 +759,7 @@ bool PngFormat::onSave(FileOp* fop)
} }
} }
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE) { else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE) {
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y); auto src_address = (const uint8_t*)img->getScanline(y);
unsigned int x; unsigned int x;
for (x=0; x<width; ++x) for (x=0; x<width; ++x)
@ -771,7 +774,7 @@ bool PngFormat::onSave(FileOp* fop)
png_free(png, row_pointer); png_free(png, row_pointer);
png_write_end(png, info); png_write_end(png, info);
if (image->pixelFormat() == IMAGE_INDEXED) { if (spec.colorMode() == ColorMode::INDEXED) {
png_free(png, palette); png_free(png, palette);
palette = nullptr; palette = nullptr;
} }

View File

@ -81,7 +81,7 @@ bool SvgFormat::onLoad(FileOp* fop)
bool SvgFormat::onSave(FileOp* fop) bool SvgFormat::onSave(FileOp* fop)
{ {
const Image* image = fop->sequenceImage(); const ImageRef image = fop->sequenceImage();
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);
@ -103,7 +103,7 @@ bool SvgFormat::onSave(FileOp* fop)
case IMAGE_RGB: { case IMAGE_RGB: {
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<RgbTraits>(image, x, y); c = get_pixel_fast<RgbTraits>(image.get(), x, y);
alpha = rgba_geta(c); alpha = rgba_geta(c);
if (alpha != 0x00) if (alpha != 0x00)
printcol(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, pixelScaleValue); printcol(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, pixelScaleValue);
@ -115,7 +115,7 @@ bool SvgFormat::onSave(FileOp* fop)
case IMAGE_GRAYSCALE: { case IMAGE_GRAYSCALE: {
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<GrayscaleTraits>(image, x, y); c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y);
auto v = graya_getv(c); auto v = graya_getv(c);
alpha = graya_geta(c); alpha = graya_geta(c);
if (alpha != 0x00) if (alpha != 0x00)
@ -142,7 +142,7 @@ bool SvgFormat::onSave(FileOp* fop)
} }
for (y=0; y<image->height(); y++) { for (y=0; y<image->height(); y++) {
for (x=0; x<image->width(); x++) { for (x=0; x<image->width(); x++) {
c = get_pixel_fast<IndexedTraits>(image, x, y); c = get_pixel_fast<IndexedTraits>(image.get(), x, y);
if (c != mask_color) if (c != mask_color)
printcol(x, y, image_palette[c][0] & 0xff, printcol(x, y, image_palette[c][0] & 0xff,
image_palette[c][1] & 0xff, image_palette[c][1] & 0xff,

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2019-2022 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
@ -53,7 +53,8 @@ class TgaFormat : public FileFormat {
FILE_SUPPORT_INDEXED | FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES | FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_GET_FORMAT_OPTIONS | FILE_SUPPORT_GET_FORMAT_OPTIONS |
FILE_SUPPORT_PALETTE_WITH_ALPHA; FILE_SUPPORT_PALETTE_WITH_ALPHA |
FILE_ENCODE_ABSTRACT_IMAGE;
} }
bool onLoad(FileOp* fop) override; bool onLoad(FileOp* fop) override;
@ -164,7 +165,7 @@ bool TgaFormat::onLoad(FileOp* fop)
if (decoder.hasAlpha()) if (decoder.hasAlpha())
fop->sequenceSetHasAlpha(true); fop->sequenceSetHasAlpha(true);
Image* image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(), ImageRef image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
spec.width(), spec.width(),
spec.height()); spec.height());
if (!image) if (!image)
@ -188,7 +189,7 @@ bool TgaFormat::onLoad(FileOp* fop)
// Post process gray image pixels (because we use grayscale images // Post process gray image pixels (because we use grayscale images
// with alpha). // with alpha).
if (header.isGray()) { if (header.isGray()) {
doc::LockImageBits<GrayscaleTraits> bits(image); doc::LockImageBits<GrayscaleTraits> bits(image.get());
for (auto it=bits.begin(), end=bits.end(); it != end; ++it) { for (auto it=bits.begin(), end=bits.end(); it != end; ++it) {
*it = doc::graya(*it, 255); *it = doc::graya(*it, 255);
} }
@ -217,7 +218,7 @@ bool TgaFormat::onLoad(FileOp* fop)
namespace { namespace {
void prepare_header(tga::Header& header, void prepare_header(tga::Header& header,
const doc::Image* image, const doc::ImageSpec& spec,
const doc::Palette* palette, const doc::Palette* palette,
const bool isOpaque, const bool isOpaque,
const bool compressed, const bool compressed,
@ -231,13 +232,13 @@ void prepare_header(tga::Header& header,
header.colormapDepth = 0; header.colormapDepth = 0;
header.xOrigin = 0; header.xOrigin = 0;
header.yOrigin = 0; header.yOrigin = 0;
header.width = image->width(); header.width = spec.width();
header.height = image->height(); header.height = spec.height();
header.bitsPerPixel = 0; header.bitsPerPixel = 0;
// TODO make this option configurable // TODO make this option configurable
header.imageDescriptor = 0x20; // Top-to-bottom header.imageDescriptor = 0x20; // Top-to-bottom
switch (image->colorMode()) { switch (spec.colorMode()) {
case ColorMode::RGB: case ColorMode::RGB:
header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb); header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb);
header.bitsPerPixel = (bitsPerPixel > 8 ? header.bitsPerPixel = (bitsPerPixel > 8 ?
@ -287,7 +288,7 @@ void prepare_header(tga::Header& header,
bool TgaFormat::onSave(FileOp* fop) bool TgaFormat::onSave(FileOp* fop)
{ {
const Image* image = fop->sequenceImage(); const FileAbstractImage* img = fop->abstractImage();
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"));
@ -297,7 +298,7 @@ bool TgaFormat::onSave(FileOp* fop)
const auto tgaOptions = std::static_pointer_cast<TgaOptions>(fop->formatOptions()); const auto tgaOptions = std::static_pointer_cast<TgaOptions>(fop->formatOptions());
prepare_header( prepare_header(
header, image, palette, header, img->spec(), palette,
// Is alpha channel required? // Is alpha channel required?
fop->document()->sprite()->isOpaque(), fop->document()->sprite()->isOpaque(),
// Compressed by default // Compressed by default
@ -307,6 +308,7 @@ bool TgaFormat::onSave(FileOp* fop)
encoder.writeHeader(header); encoder.writeHeader(header);
doc::ImageRef image = img->getScaledImage();
tga::Image tgaImage; tga::Image tgaImage;
tgaImage.pixels = image->getPixelAddress(0, 0); tgaImage.pixels = image->getPixelAddress(0, 0);
tgaImage.rowstride = image->getRowStrideSize(); tgaImage.rowstride = image->getRowStrideSize();

View File

@ -141,6 +141,12 @@ bool ExportFileWindow::isForTwitter() const
return forTwitter()->isSelected(); return forTwitter()->isSelected();
} }
void ExportFileWindow::setResizeScale(const gfx::PointF& scale)
{
resize()->setValue(
base::convert_to<std::string>(scale.x)); // TODO support x & y
}
void ExportFileWindow::setAniDir(const doc::AniDir aniDir) void ExportFileWindow::setAniDir(const doc::AniDir aniDir)
{ {
anidir()->setSelectedItemIndex(int(aniDir)); anidir()->setSelectedItemIndex(int(aniDir));

View File

@ -34,12 +34,13 @@ namespace app {
bool applyPixelRatio() const; bool applyPixelRatio() const;
bool isForTwitter() const; bool isForTwitter() const;
void setOutputFilename(const std::string& pathAndFilename);
void setResizeScale(const gfx::PointF& scale);
void setAniDir(const doc::AniDir aniDir); void setAniDir(const doc::AniDir aniDir);
obs::signal<std::string()> SelectOutputFile; obs::signal<std::string()> SelectOutputFile;
private: private:
void setOutputFilename(const std::string& pathAndFilename);
void updateOutputFilenameEntry(); void updateOutputFilenameEntry();
void onOutputFilenameEntryChange(); void onOutputFilenameEntryChange();
void updateAniDir(); void updateAniDir();