mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-22 05:43:32 +00:00
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:
parent
c58dae51fa
commit
4aa5fedfec
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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, ...)
|
||||||
|
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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++));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 */
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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));
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user