Merge branch 'main' into beta

This commit is contained in:
David Capello 2022-06-15 13:44:34 -03:00
commit caf475b2dc
28 changed files with 605 additions and 259 deletions

View File

@ -1,4 +1,5 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2022 by Igara Studio S.A. -->
<!-- Copyright (C) 2016-2018 by David Capello --> <!-- Copyright (C) 2016-2018 by David Capello -->
<gui> <gui>
<window id="export_file" text="@.title"> <window id="export_file" text="@.title">
@ -8,19 +9,20 @@
<button id="output_filename_browse" text="..." style="mini_button" /> <button id="output_filename_browse" text="..." style="mini_button" />
<label id="resize_label" text="@.resize" /> <label id="resize_label" text="@.resize" />
<combobox id="resize" cell_align="horizontal" cell_hspan="2"> <combobox id="resize" editable="true" suffix="%"
<listitem text="25%" value="0.25" /> cell_align="horizontal" cell_hspan="2">
<listitem text="50%" value="0.5" /> <listitem text="25" />
<listitem text="100%" value="1" /> <listitem text="50" />
<listitem text="200%" value="2" /> <listitem text="100" />
<listitem text="300%" value="3" /> <listitem text="200" />
<listitem text="400%" value="4" /> <listitem text="300" />
<listitem text="500%" value="5" /> <listitem text="400" />
<listitem text="600%" value="6" /> <listitem text="500" />
<listitem text="700%" value="7" /> <listitem text="600" />
<listitem text="800%" value="8" /> <listitem text="700" />
<listitem text="900%" value="9" /> <listitem text="800" />
<listitem text="1000%" value="10" /> <listitem text="900" />
<listitem text="1000" />
</combobox> </combobox>
<label id="layers_label" text="@.layers" /> <label id="layers_label" text="@.layers" />

View File

@ -554,7 +554,7 @@ bool AppMenus::rebuildRecentList()
else { else {
std::unique_ptr<AppMenuItem> menuitem( std::unique_ptr<AppMenuItem> menuitem(
new AppMenuItem( new AppMenuItem(
Strings::main_menu_file_no_recent_file(), nullptr)); Strings::main_menu_file_no_recent_file()));
menuitem->setIsRecentFileItem(true); menuitem->setIsRecentFileItem(true);
menuitem->setEnabled(false); menuitem->setEnabled(false);

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
@ -86,24 +86,18 @@ private:
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
SaveFileBaseCommand::SaveFileBaseCommand(const char* id, CommandFlags flags) SaveFileBaseCommand::SaveFileBaseCommand(const char* id, CommandFlags flags)
: Command(id, flags) : CommandWithNewParams<SaveFileParams>(id, flags)
{ {
m_useUI = true;
m_ignoreEmpty = false;
} }
void SaveFileBaseCommand::onLoadParams(const Params& params) void SaveFileBaseCommand::onLoadParams(const Params& params)
{ {
m_filename = params.get("filename"); CommandWithNewParams<SaveFileParams>::onLoadParams(params);
m_filenameFormat = params.get("filename-format");
m_tag = params.get("frame-tag");
m_aniDir = params.get("ani-dir");
m_slice = params.get("slice");
if (params.has_param("from-frame") || if (this->params().fromFrame.isSet() ||
params.has_param("to-frame")) { this->params().toFrame.isSet()) {
doc::frame_t fromFrame = params.get_as<doc::frame_t>("from-frame"); doc::frame_t fromFrame = this->params().fromFrame();
doc::frame_t toFrame = params.get_as<doc::frame_t>("to-frame"); doc::frame_t toFrame = this->params().toFrame();
m_selFrames.insert(fromFrame, toFrame); m_selFrames.insert(fromFrame, toFrame);
m_adjustFramesByTag = true; m_adjustFramesByTag = true;
} }
@ -111,11 +105,6 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
m_selFrames.clear(); m_selFrames.clear();
m_adjustFramesByTag = false; m_adjustFramesByTag = false;
} }
std::string useUI = params.get("useUI");
m_useUI = (useUI.empty() || (useUI == "true"));
m_ignoreEmpty = params.get_as<bool>("ignoreEmpty");
} }
// Returns true if there is a current sprite to save. // Returns true if there is a current sprite to save.
@ -129,8 +118,8 @@ std::string SaveFileBaseCommand::saveAsDialog(
Context* context, Context* context,
const std::string& dlgTitle, const std::string& dlgTitle,
const std::string& initialFilename, const std::string& initialFilename,
const bool markAsSaved, const MarkAsSaved markAsSaved,
const bool saveInBackground, const SaveInBackground saveInBackground,
const std::string& forbiddenFilename) const std::string& forbiddenFilename)
{ {
Doc* document = context->activeDocument(); Doc* document = context->activeDocument();
@ -141,36 +130,38 @@ std::string SaveFileBaseCommand::saveAsDialog(
// preferences. // preferences.
Preferences::instance().save(); Preferences::instance().save();
std::string filename; std::string filename = params().filename();
if (filename.empty() || params().ui()) {
if (!m_filename.empty()) {
filename = m_filename;
}
else {
base::paths exts = get_writable_extensions(); base::paths exts = get_writable_extensions();
filename = initialFilename; filename = initialFilename;
#ifdef ENABLE_UI #ifdef ENABLE_UI
again:; if (context->isUIAvailable()) {
base::paths newfilename; again:;
if (!m_useUI || base::paths newfilename;
!app::show_file_selector( if (!params().ui() ||
dlgTitle, filename, exts, !app::show_file_selector(
FileSelectorType::Save, dlgTitle, filename, exts,
newfilename)) FileSelectorType::Save,
return std::string(); newfilename)) {
return std::string();
}
filename = newfilename.front(); filename = newfilename.front();
if (!forbiddenFilename.empty() && if (!forbiddenFilename.empty() &&
base::normalize_path(forbiddenFilename) == base::normalize_path(forbiddenFilename) ==
base::normalize_path(filename)) { base::normalize_path(filename)) {
ui::Alert::show(Strings::alerts_cannot_file_overwrite_on_export()); ui::Alert::show(Strings::alerts_cannot_file_overwrite_on_export());
goto again; goto again;
}
} }
#endif // ENABLE_UI #endif // ENABLE_UI
} }
if (saveInBackground) { if (filename.empty())
return std::string();
if (saveInBackground == SaveInBackground::On) {
saveDocumentInBackground( saveDocumentInBackground(
context, document, context, document,
filename, markAsSaved); filename, markAsSaved);
@ -198,10 +189,12 @@ void SaveFileBaseCommand::saveDocumentInBackground(
const Context* context, const Context* context,
Doc* document, Doc* document,
const std::string& filename, const std::string& filename,
const bool markAsSaved) const MarkAsSaved markAsSaved,
const ResizeOnTheFly resizeOnTheFly,
const gfx::PointF& scale)
{ {
if (!m_aniDir.empty()) { if (params().aniDir.isSet()) {
switch (convert_string_to_anidir(m_aniDir)) { switch (params().aniDir()) {
case AniDir::REVERSE: case AniDir::REVERSE:
m_selFrames = m_selFrames.makeReverse(); m_selFrames = m_selFrames.makeReverse();
break; break;
@ -211,7 +204,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(
} }
} }
FileOpROI roi(document, m_slice, m_tag, FileOpROI roi(document, params().slice(), params().tag(),
m_selFrames, m_adjustFramesByTag); m_selFrames, m_adjustFramesByTag);
std::unique_ptr<FileOp> fop( std::unique_ptr<FileOp> fop(
@ -219,11 +212,14 @@ void SaveFileBaseCommand::saveDocumentInBackground(
context, context,
roi, roi,
filename, filename,
m_filenameFormat, params().filenameFormat(),
m_ignoreEmpty)); params().ignoreEmpty()));
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();
@ -239,17 +235,22 @@ void SaveFileBaseCommand::saveDocumentInBackground(
else if (fop->isStop()) { else if (fop->isStop()) {
document->impossibleToBackToSavedState(); document->impossibleToBackToSavedState();
} }
else if (context->isUIAvailable()) { else {
App::instance()->recentFiles()->addRecentFile(filename); if (context->isUIAvailable() && params().ui())
if (markAsSaved) { App::instance()->recentFiles()->addRecentFile(filename);
if (markAsSaved == MarkAsSaved::On) {
document->markAsSaved(); document->markAsSaved();
document->setFilename(filename); document->setFilename(filename);
document->incrementVersion(); document->incrementVersion();
} }
#ifdef ENABLE_UI #ifdef ENABLE_UI
StatusBar::instance()->setStatusText( if (context->isUIAvailable() && params().ui()) {
2000, fmt::format("File <{}> saved.", StatusBar::instance()->setStatusText(
base::get_file_name(filename))); 2000, fmt::format("File <{}> saved.",
base::get_file_name(filename)));
}
#endif #endif
} }
} }
@ -283,14 +284,18 @@ void SaveFileCommand::onExecute(Context* context)
saveDocumentInBackground( saveDocumentInBackground(
context, document, context, document,
documentReader->filename(), true); (params().filename.isSet() ? params().filename():
documentReader->filename()),
MarkAsSaved::On);
} }
// If the document isn't associated to a file, we must to show the // If the document isn't associated to a file, we must to show the
// save-as dialog to the user to select for first time the file-name // save-as dialog to the user to select for first time the file-name
// for this document. // for this document.
else { else {
saveAsDialog(context, "Save File", saveAsDialog(context, "Save File",
document->filename(), true); (params().filename.isSet() ? params().filename():
document->filename()),
MarkAsSaved::On);
} }
} }
@ -311,7 +316,9 @@ void SaveFileAsCommand::onExecute(Context* context)
{ {
Doc* document = context->activeDocument(); Doc* document = context->activeDocument();
saveAsDialog(context, "Save As", saveAsDialog(context, "Save As",
document->filename(), true); (params().filename.isSet() ? params().filename():
document->filename()),
MarkAsSaved::On);
} }
class SaveFileCopyAsCommand : public SaveFileBaseCommand { class SaveFileCopyAsCommand : public SaveFileBaseCommand {
@ -334,17 +341,16 @@ SaveFileCopyAsCommand::SaveFileCopyAsCommand()
void SaveFileCopyAsCommand::onExecute(Context* context) void SaveFileCopyAsCommand::onExecute(Context* context)
{ {
Doc* doc = context->activeDocument(); Doc* doc = context->activeDocument();
std::string outputFilename = m_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;
doc::AniDir aniDirValue = convert_string_to_anidir(m_aniDir); double scale = params().scale();
doc::AniDir aniDirValue = params().aniDir();
bool isForTwitter = false; bool isForTwitter = false;
#if ENABLE_UI #if ENABLE_UI
if (m_useUI && context->isUIAvailable()) { if (params().ui() && context->isUIAvailable()) {
ExportFileWindow win(doc); ExportFileWindow win(doc);
bool askOverwrite = true; bool askOverwrite = true;
@ -353,7 +359,9 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
std::string result = std::string result =
saveAsDialog( saveAsDialog(
context, "Export", context, "Export",
win.outputFilenameValue(), false, false, win.outputFilenameValue(),
MarkAsSaved::Off,
SaveInBackground::Off,
(doc->isAssociatedToFile() ? doc->filename(): (doc->isAssociatedToFile() ? doc->filename():
std::string())); std::string()));
if (!result.empty()) if (!result.empty())
@ -362,6 +370,18 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
return result; return result;
}); });
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");
again:; again:;
@ -389,31 +409,36 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
layers = win.layersValue(); layers = win.layersValue();
frames = win.framesValue(); frames = win.framesValue();
xscale = yscale = win.resizeValue(); scale = win.resizeValue();
applyPixelRatio = win.applyPixelRatio(); applyPixelRatio = win.applyPixelRatio();
aniDirValue = win.aniDirValue(); aniDirValue = win.aniDirValue();
isForTwitter = win.isForTwitter(); isForTwitter = win.isForTwitter();
} }
#endif #endif
gfx::PointF scaleXY(scale, scale);
// Pixel ratio // Pixel ratio
if (applyPixelRatio) { if (applyPixelRatio) {
doc::PixelRatio pr = doc->sprite()->pixelRatio(); doc::PixelRatio pr = doc->sprite()->pixelRatio();
xscale *= pr.w; scaleXY.x *= pr.w;
yscale *= pr.h; scaleXY.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 && (scaleXY.x != 1.0 ||
scaleXY.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) * scaleXY.x);
int newHeight = int(double(height) * yscale); int newHeight = int(double(height) * scaleXY.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) {
@ -441,27 +466,33 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
layers, layers,
layersVisibility); layersVisibility);
// Selected frames to export // m_selFrames is not empty if fromFrame/toFrame parameters are
SelectedFrames selFrames; // specified.
Tag* tag = calculate_selected_frames( if (m_selFrames.empty()) {
site, frames, selFrames); // Selected frames to export
if (tag) SelectedFrames selFrames;
m_tag = tag->name(); Tag* tag = calculate_selected_frames(
m_selFrames = selFrames; site, frames, selFrames);
if (tag)
params().tag(tag->name());
m_selFrames = selFrames;
}
m_adjustFramesByTag = false; m_adjustFramesByTag = false;
} }
base::ScopedValue<std::string> restoreAniDir( // Set ani dir
m_aniDir, params().aniDir(aniDirValue);
convert_anidir_to_string(aniDirValue), // New value
m_aniDir); // Restore old value
// TODO This should be set as options for the specific encoder // TODO This should be set as options for the specific encoder
GifEncoderDurationFix fixGif(isForTwitter); GifEncoderDurationFix fixGif(isForTwitter);
PngEncoderOneAlphaPixel fixPng(isForTwitter); PngEncoderOneAlphaPixel fixPng(isForTwitter);
saveDocumentInBackground( saveDocumentInBackground(
context, doc, outputFilename, false); context, doc, outputFilename,
MarkAsSaved::Off,
(resizeOnTheFly ? ResizeOnTheFly::On:
ResizeOnTheFly::Off),
scaleXY);
} }
// Undo resize // Undo resize

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// 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
@ -9,15 +10,35 @@
#pragma once #pragma once
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/commands/new_params.h"
#include "doc/anidir.h"
#include "doc/selected_frames.h" #include "doc/selected_frames.h"
#include "gfx/point.h"
#include <string> #include <string>
namespace app { namespace app {
class Doc; class Doc;
class SaveFileBaseCommand : public Command { struct SaveFileParams : public NewParams {
Param<bool> ui { this, true, { "ui", "useUI" } };
Param<std::string> filename { this, std::string(), "filename" };
Param<std::string> filenameFormat { this, std::string(), { "filenameFormat", "filename-format" } };
Param<std::string> tag { this, std::string(), { "tag", "frame-tag" } };
Param<doc::AniDir> aniDir { this, doc::AniDir::FORWARD, { "aniDir", "ani-dir" } };
Param<std::string> slice { this, std::string(), "slice" };
Param<doc::frame_t> fromFrame { this, 0, { "fromFrame", "from-frame" } };
Param<doc::frame_t> toFrame { this, 0, { "toFrame", "to-frame" } };
Param<bool> ignoreEmpty { this, false, "ignoreEmpty" };
Param<double> scale { this, 1.0, "scale" };
};
class SaveFileBaseCommand : public CommandWithNewParams<SaveFileParams> {
public: public:
enum class MarkAsSaved { Off, On };
enum class SaveInBackground { Off, On };
enum class ResizeOnTheFly { Off, On };
SaveFileBaseCommand(const char* id, CommandFlags flags); SaveFileBaseCommand(const char* id, CommandFlags flags);
protected: protected:
@ -28,24 +49,19 @@ namespace app {
Context* context, Context* context,
const std::string& dlgTitle, const std::string& dlgTitle,
const std::string& filename, const std::string& filename,
const bool markAsSaved, const MarkAsSaved markAsSaved,
const bool saveInBackground = true, const SaveInBackground saveInBackground = SaveInBackground::On,
const std::string& forbiddenFilename = std::string()); const std::string& forbiddenFilename = std::string());
void saveDocumentInBackground( void saveDocumentInBackground(
const Context* context, const Context* context,
Doc* document, Doc* document,
const std::string& filename, const std::string& filename,
const bool markAsSaved); const MarkAsSaved markAsSaved,
const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off,
const gfx::PointF& scale = gfx::PointF(1.0, 1.0));
std::string m_filename;
std::string m_filenameFormat;
std::string m_tag;
std::string m_aniDir;
std::string m_slice;
doc::SelectedFrames m_selFrames; doc::SelectedFrames m_selFrames;
bool m_adjustFramesByTag; bool m_adjustFramesByTag;
bool m_useUI;
bool m_ignoreEmpty;
}; };
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -18,6 +18,7 @@
#include "base/split_string.h" #include "base/split_string.h"
#include "base/string.h" #include "base/string.h"
#include "doc/algorithm/resize_image.h" #include "doc/algorithm/resize_image.h"
#include "doc/anidir.h"
#include "doc/color_mode.h" #include "doc/color_mode.h"
#include "doc/rgbmap_algorithm.h" #include "doc/rgbmap_algorithm.h"
#include "filters/color_curve.h" #include "filters/color_curve.h"
@ -145,6 +146,12 @@ void Param<doc::ColorMode>::fromString(const std::string& value)
setValue(doc::ColorMode::RGB); setValue(doc::ColorMode::RGB);
} }
template<>
void Param<doc::AniDir>::fromString(const std::string& value)
{
setValue(convert_string_to_anidir(value));
}
template<> template<>
void Param<app::Color>::fromString(const std::string& value) void Param<app::Color>::fromString(const std::string& value)
{ {
@ -315,6 +322,15 @@ void Param<doc::ColorMode>::fromLua(lua_State* L, int index)
setValue((doc::ColorMode)lua_tointeger(L, index)); setValue((doc::ColorMode)lua_tointeger(L, index));
} }
template<>
void Param<doc::AniDir>::fromLua(lua_State* L, int index)
{
if (lua_type(L, index) == LUA_TSTRING)
fromString(lua_tostring(L, index));
else
setValue((doc::AniDir)lua_tointeger(L, index));
}
template<> template<>
void Param<app::Color>::fromLua(lua_State* L, int index) void Param<app::Color>::fromLua(lua_State* L, int index)
{ {

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,34 +689,34 @@ 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) {
return false; return false;
} }
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,31 +853,37 @@ 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++)

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
@ -775,6 +912,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,
@ -1082,7 +1222,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;
@ -1120,7 +1260,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-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
@ -93,7 +93,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;
@ -954,10 +955,11 @@ 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->isOpaque()) , 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_globalColormapPalette(*m_sprite->palette(0)) , m_globalColormapPalette(*m_sprite->palette(0))
@ -973,7 +975,7 @@ public:
m_lastFrameBounds = m_spriteBounds; m_lastFrameBounds = m_spriteBounds;
m_lastDisposal = DisposalMethod::NONE; m_lastDisposal = DisposalMethod::NONE;
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);
@ -983,8 +985,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.
bool quantizeColormaps = false; bool quantizeColormaps = false;
@ -1001,7 +1003,7 @@ public:
if (!quantizeColormaps) { if (!quantizeColormaps) {
m_globalColormap = createColorMap(&m_globalColormapPalette); m_globalColormap = createColorMap(&m_globalColormapPalette);
m_bgIndex = m_sprite->transparentColor(); m_bgIndex = m_spec.maskColor();
// For indexed and opaque sprite, we can preserve the exact // For indexed and opaque sprite, we can preserve the exact
// palette order without lossing compression rate. // palette order without lossing compression rate.
if (m_hasBackground) if (m_hasBackground)
@ -1025,7 +1027,7 @@ public:
m_transparentIndex = (m_hasBackground ? -1: m_bgIndex); m_transparentIndex = (m_hasBackground ? -1: m_bgIndex);
if (m_globalColormap) { if (m_globalColormap) {
// The variable m_globalColormap is != nullptr only on indexed images // The variable m_globalColormap is != nullptr only on indexed images
ASSERT(m_sprite->pixelFormat() == IMAGE_INDEXED); ASSERT(m_spec.colorMode() == ColorMode::INDEXED);
const Palette* pal = m_sprite->palette(0); const Palette* pal = m_sprite->palette(0);
bool maskColorFounded = false; bool maskColorFounded = false;
@ -1331,7 +1333,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
@ -1550,15 +1552,11 @@ 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);
if (m_preservePaletteOrder) if (m_preservePaletteOrder)
clear_image(dst, m_bgIndex); clear_image(dst, m_bgIndex);
else else
clear_image(dst, 0); clear_image(dst, 0);
render.renderSprite(dst, m_sprite, frame); m_img->renderFrame(frame, dst);
} }
private: private:
@ -1568,8 +1566,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;
@ -1590,8 +1587,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,16 +106,16 @@ 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);
if (!image) { if (!image) {
return false; return false;
} }
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,9 +165,9 @@ 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)
return false; return false;
@ -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

@ -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
@ -112,7 +112,10 @@ void Preferences::load()
void Preferences::save() void Preferences::save()
{ {
ui::assert_ui_thread(); #ifdef _DEBUG
if (ui::UISystem::instance())
ui::assert_ui_thread();
#endif
app::gen::GlobalPref::save(); app::gen::GlobalPref::save();
for (auto& pair : m_tools) for (auto& pair : m_tools)

View File

@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two // Increment this value if the scripting API is modified between two
// released Aseprite versions. // released Aseprite versions.
#define API_VERSION 18 #define API_VERSION 19
#endif #endif

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) 2015-2018 David Capello // Copyright (C) 2015-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -105,8 +105,29 @@ int Image_new(lua_State* L)
if (auto spec2 = may_get_obj<doc::ImageSpec>(L, 1)) { if (auto spec2 = may_get_obj<doc::ImageSpec>(L, 1)) {
spec = *spec2; spec = *spec2;
} }
else if (may_get_obj<ImageObj>(L, 1)) { else if (auto imgObj = may_get_obj<ImageObj>(L, 1)) {
return Image_clone(L); // Copy a region of the image
if (auto rc = may_get_obj<gfx::Rect>(L, 2)) {
doc::Image* crop = nullptr;
try {
auto docImg = imgObj->image(L);
crop = doc::crop_image(docImg, *rc, docImg->maskColor());
}
catch (const std::invalid_argument&) {
// Do nothing (will return nil)
}
if (crop) {
push_new<ImageObj>(L, crop);
return 1;
}
else {
return 0;
}
}
// Copy the whole image
else {
return Image_clone(L);
}
} }
else if (auto spr = may_get_docobj<doc::Sprite>(L, 1)) { else if (auto spr = may_get_docobj<doc::Sprite>(L, 1)) {
image = doc::Image::create(spr->spec()); image = doc::Image::create(spr->spec());

View File

@ -35,6 +35,7 @@ namespace app {
AppMenuItem(const std::string& text, AppMenuItem(const std::string& text,
const std::string& commandId = std::string(), const std::string& commandId = std::string(),
const Params& params = Params()); const Params& params = Params());
AppMenuItem(const std::string& text, std::nullptr_t) = delete;
KeyPtr key() { return m_key; } KeyPtr key() { return m_key; }
void setKey(const KeyPtr& key); void setKey(const KeyPtr& key);

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) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -51,8 +51,7 @@ ExportFileWindow::ExportFileWindow(const Doc* doc)
} }
// Default export configuration // Default export configuration
resize()->setValue( setResizeScale(m_docPref.saveCopy.resizeScale());
base::convert_to<std::string>(m_docPref.saveCopy.resizeScale()));
fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer()); fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer());
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag()); fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir()); fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
@ -113,7 +112,8 @@ std::string ExportFileWindow::outputFilenameValue() const
double ExportFileWindow::resizeValue() const double ExportFileWindow::resizeValue() const
{ {
return base::convert_to<double>(resize()->getValue()); double value = resize()->getEntryWidget()->textDouble() / 100.0;
return std::clamp(value, 0.001, 100000000.0);
} }
std::string ExportFileWindow::layersValue() const std::string ExportFileWindow::layersValue() const
@ -141,6 +141,16 @@ bool ExportFileWindow::isForTwitter() const
return forTwitter()->isSelected(); return forTwitter()->isSelected();
} }
void ExportFileWindow::setResizeScale(double scale)
{
resize()->setValue(fmt::format("{:.2f}", 100.0 * scale));
}
void ExportFileWindow::setAniDir(const doc::AniDir aniDir)
{
anidir()->setSelectedItemIndex(int(aniDir));
}
void ExportFileWindow::setOutputFilename(const std::string& pathAndFilename) void ExportFileWindow::setOutputFilename(const std::string& pathAndFilename)
{ {
m_outputPath = base::get_file_path(pathAndFilename); m_outputPath = base::get_file_path(pathAndFilename);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -34,10 +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 double scale);
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();

View File

@ -267,6 +267,11 @@ void ExpandCelCanvas::commit()
// And add the cel again in the layer. // And add the cel again in the layer.
m_cmds->executeAndAdd(new cmd::AddCel(m_layer, m_cel)); m_cmds->executeAndAdd(new cmd::AddCel(m_layer, m_cel));
} }
else {
// Delete unused cel
delete m_cel;
m_cel = nullptr;
}
} }
// We are selecting... // We are selecting...
else { else {

View File

@ -231,6 +231,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
bool editable = bool_attr(elem, "editable", false); bool editable = bool_attr(elem, "editable", false);
if (editable) if (editable)
((ComboBox*)widget)->setEditable(true); ((ComboBox*)widget)->setEditable(true);
const char* suffix = elem->Attribute("suffix");
if (suffix)
((ComboBox*)widget)->getEntryWidget()->setSuffix(suffix);
} }
else if (elem_name == "entry" || else if (elem_name == "entry" ||
elem_name == "expr") { elem_name == "expr") {

View File

@ -328,7 +328,8 @@ void ComboBox::setValue(const std::string& value)
{ {
if (isEditable()) { if (isEditable()) {
m_entry->setText(value); m_entry->setText(value);
m_entry->selectAllText(); if (hasFocus())
m_entry->selectAllText();
} }
else { else {
int index = findItemIndexByValue(value); int index = findItemIndexByValue(value);
@ -560,6 +561,14 @@ bool ComboBoxEntry::onProcessMessage(Message* msg)
return result; return result;
} }
case kFocusLeaveMessage:
if (m_comboBox->isEditable() &&
m_comboBox->m_window &&
!View::getView(m_comboBox->m_listbox)->hasMouse()) {
m_comboBox->closeListBox();
}
break;
} }
return Entry::onProcessMessage(msg); return Entry::onProcessMessage(msg);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -103,7 +103,7 @@ void Overlay::captureOverlappedArea()
m_overlap->width(), m_overlap->height()); m_overlap->width(), m_overlap->height());
m_overlap->setImmutable(); m_overlap->setImmutable();
m_captured = displaySurface; m_captured = base::AddRef(displaySurface);
} }
void Overlay::restoreOverlappedArea(const gfx::Rect& restoreBounds) void Overlay::restoreOverlappedArea(const gfx::Rect& restoreBounds)
@ -118,7 +118,7 @@ void Overlay::restoreOverlappedArea(const gfx::Rect& restoreBounds)
return; return;
os::SurfaceLock lock(m_overlap.get()); os::SurfaceLock lock(m_overlap.get());
m_overlap->blitTo(m_captured, 0, 0, m_pos.x, m_pos.y, m_overlap->blitTo(m_captured.get(), 0, 0, m_pos.x, m_pos.y,
m_overlap->width(), m_overlap->height()); m_overlap->width(), m_overlap->height());
m_display->dirtyRect(bounds()); m_display->dirtyRect(bounds());

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -60,7 +60,7 @@ namespace ui {
// Surface where we captured the overlapped (m_overlap) // Surface where we captured the overlapped (m_overlap)
// region. It's nullptr if the overlay wasn't drawn yet. // region. It's nullptr if the overlay wasn't drawn yet.
os::Surface* m_captured; os::SurfaceRef m_captured;
gfx::Point m_pos; gfx::Point m_pos;
ZOrder m_zorder; ZOrder m_zorder;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A. // Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.