mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-06 03:39:51 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
caf475b2dc
@ -1,4 +1,5 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2022 by Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2016-2018 by David Capello -->
|
||||
<gui>
|
||||
<window id="export_file" text="@.title">
|
||||
@ -8,19 +9,20 @@
|
||||
<button id="output_filename_browse" text="..." style="mini_button" />
|
||||
|
||||
<label id="resize_label" text="@.resize" />
|
||||
<combobox id="resize" cell_align="horizontal" cell_hspan="2">
|
||||
<listitem text="25%" value="0.25" />
|
||||
<listitem text="50%" value="0.5" />
|
||||
<listitem text="100%" value="1" />
|
||||
<listitem text="200%" value="2" />
|
||||
<listitem text="300%" value="3" />
|
||||
<listitem text="400%" value="4" />
|
||||
<listitem text="500%" value="5" />
|
||||
<listitem text="600%" value="6" />
|
||||
<listitem text="700%" value="7" />
|
||||
<listitem text="800%" value="8" />
|
||||
<listitem text="900%" value="9" />
|
||||
<listitem text="1000%" value="10" />
|
||||
<combobox id="resize" editable="true" suffix="%"
|
||||
cell_align="horizontal" cell_hspan="2">
|
||||
<listitem text="25" />
|
||||
<listitem text="50" />
|
||||
<listitem text="100" />
|
||||
<listitem text="200" />
|
||||
<listitem text="300" />
|
||||
<listitem text="400" />
|
||||
<listitem text="500" />
|
||||
<listitem text="600" />
|
||||
<listitem text="700" />
|
||||
<listitem text="800" />
|
||||
<listitem text="900" />
|
||||
<listitem text="1000" />
|
||||
</combobox>
|
||||
|
||||
<label id="layers_label" text="@.layers" />
|
||||
|
@ -554,7 +554,7 @@ bool AppMenus::rebuildRecentList()
|
||||
else {
|
||||
std::unique_ptr<AppMenuItem> menuitem(
|
||||
new AppMenuItem(
|
||||
Strings::main_menu_file_no_recent_file(), nullptr));
|
||||
Strings::main_menu_file_no_recent_file()));
|
||||
menuitem->setIsRecentFileItem(true);
|
||||
menuitem->setEnabled(false);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -86,24 +86,18 @@ private:
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
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)
|
||||
{
|
||||
m_filename = params.get("filename");
|
||||
m_filenameFormat = params.get("filename-format");
|
||||
m_tag = params.get("frame-tag");
|
||||
m_aniDir = params.get("ani-dir");
|
||||
m_slice = params.get("slice");
|
||||
CommandWithNewParams<SaveFileParams>::onLoadParams(params);
|
||||
|
||||
if (params.has_param("from-frame") ||
|
||||
params.has_param("to-frame")) {
|
||||
doc::frame_t fromFrame = params.get_as<doc::frame_t>("from-frame");
|
||||
doc::frame_t toFrame = params.get_as<doc::frame_t>("to-frame");
|
||||
if (this->params().fromFrame.isSet() ||
|
||||
this->params().toFrame.isSet()) {
|
||||
doc::frame_t fromFrame = this->params().fromFrame();
|
||||
doc::frame_t toFrame = this->params().toFrame();
|
||||
m_selFrames.insert(fromFrame, toFrame);
|
||||
m_adjustFramesByTag = true;
|
||||
}
|
||||
@ -111,11 +105,6 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
|
||||
m_selFrames.clear();
|
||||
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.
|
||||
@ -129,8 +118,8 @@ std::string SaveFileBaseCommand::saveAsDialog(
|
||||
Context* context,
|
||||
const std::string& dlgTitle,
|
||||
const std::string& initialFilename,
|
||||
const bool markAsSaved,
|
||||
const bool saveInBackground,
|
||||
const MarkAsSaved markAsSaved,
|
||||
const SaveInBackground saveInBackground,
|
||||
const std::string& forbiddenFilename)
|
||||
{
|
||||
Doc* document = context->activeDocument();
|
||||
@ -141,36 +130,38 @@ std::string SaveFileBaseCommand::saveAsDialog(
|
||||
// preferences.
|
||||
Preferences::instance().save();
|
||||
|
||||
std::string filename;
|
||||
|
||||
if (!m_filename.empty()) {
|
||||
filename = m_filename;
|
||||
}
|
||||
else {
|
||||
std::string filename = params().filename();
|
||||
if (filename.empty() || params().ui()) {
|
||||
base::paths exts = get_writable_extensions();
|
||||
filename = initialFilename;
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
again:;
|
||||
base::paths newfilename;
|
||||
if (!m_useUI ||
|
||||
!app::show_file_selector(
|
||||
dlgTitle, filename, exts,
|
||||
FileSelectorType::Save,
|
||||
newfilename))
|
||||
return std::string();
|
||||
if (context->isUIAvailable()) {
|
||||
again:;
|
||||
base::paths newfilename;
|
||||
if (!params().ui() ||
|
||||
!app::show_file_selector(
|
||||
dlgTitle, filename, exts,
|
||||
FileSelectorType::Save,
|
||||
newfilename)) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
filename = newfilename.front();
|
||||
if (!forbiddenFilename.empty() &&
|
||||
base::normalize_path(forbiddenFilename) ==
|
||||
base::normalize_path(filename)) {
|
||||
ui::Alert::show(Strings::alerts_cannot_file_overwrite_on_export());
|
||||
goto again;
|
||||
filename = newfilename.front();
|
||||
if (!forbiddenFilename.empty() &&
|
||||
base::normalize_path(forbiddenFilename) ==
|
||||
base::normalize_path(filename)) {
|
||||
ui::Alert::show(Strings::alerts_cannot_file_overwrite_on_export());
|
||||
goto again;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_UI
|
||||
}
|
||||
|
||||
if (saveInBackground) {
|
||||
if (filename.empty())
|
||||
return std::string();
|
||||
|
||||
if (saveInBackground == SaveInBackground::On) {
|
||||
saveDocumentInBackground(
|
||||
context, document,
|
||||
filename, markAsSaved);
|
||||
@ -198,10 +189,12 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
const Context* context,
|
||||
Doc* document,
|
||||
const std::string& filename,
|
||||
const bool markAsSaved)
|
||||
const MarkAsSaved markAsSaved,
|
||||
const ResizeOnTheFly resizeOnTheFly,
|
||||
const gfx::PointF& scale)
|
||||
{
|
||||
if (!m_aniDir.empty()) {
|
||||
switch (convert_string_to_anidir(m_aniDir)) {
|
||||
if (params().aniDir.isSet()) {
|
||||
switch (params().aniDir()) {
|
||||
case AniDir::REVERSE:
|
||||
m_selFrames = m_selFrames.makeReverse();
|
||||
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);
|
||||
|
||||
std::unique_ptr<FileOp> fop(
|
||||
@ -219,11 +212,14 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
context,
|
||||
roi,
|
||||
filename,
|
||||
m_filenameFormat,
|
||||
m_ignoreEmpty));
|
||||
params().filenameFormat(),
|
||||
params().ignoreEmpty()));
|
||||
if (!fop)
|
||||
return;
|
||||
|
||||
if (resizeOnTheFly == ResizeOnTheFly::On)
|
||||
fop->setOnTheFlyScale(scale);
|
||||
|
||||
SaveFileJob job(fop.get());
|
||||
job.showProgressWindow();
|
||||
|
||||
@ -239,17 +235,22 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
else if (fop->isStop()) {
|
||||
document->impossibleToBackToSavedState();
|
||||
}
|
||||
else if (context->isUIAvailable()) {
|
||||
App::instance()->recentFiles()->addRecentFile(filename);
|
||||
if (markAsSaved) {
|
||||
else {
|
||||
if (context->isUIAvailable() && params().ui())
|
||||
App::instance()->recentFiles()->addRecentFile(filename);
|
||||
|
||||
if (markAsSaved == MarkAsSaved::On) {
|
||||
document->markAsSaved();
|
||||
document->setFilename(filename);
|
||||
document->incrementVersion();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
StatusBar::instance()->setStatusText(
|
||||
2000, fmt::format("File <{}> saved.",
|
||||
base::get_file_name(filename)));
|
||||
if (context->isUIAvailable() && params().ui()) {
|
||||
StatusBar::instance()->setStatusText(
|
||||
2000, fmt::format("File <{}> saved.",
|
||||
base::get_file_name(filename)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -283,14 +284,18 @@ void SaveFileCommand::onExecute(Context* context)
|
||||
|
||||
saveDocumentInBackground(
|
||||
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
|
||||
// save-as dialog to the user to select for first time the file-name
|
||||
// for this document.
|
||||
else {
|
||||
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();
|
||||
saveAsDialog(context, "Save As",
|
||||
document->filename(), true);
|
||||
(params().filename.isSet() ? params().filename():
|
||||
document->filename()),
|
||||
MarkAsSaved::On);
|
||||
}
|
||||
|
||||
class SaveFileCopyAsCommand : public SaveFileBaseCommand {
|
||||
@ -334,17 +341,16 @@ SaveFileCopyAsCommand::SaveFileCopyAsCommand()
|
||||
void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
{
|
||||
Doc* doc = context->activeDocument();
|
||||
std::string outputFilename = m_filename;
|
||||
std::string outputFilename = params().filename();
|
||||
std::string layers = kAllLayers;
|
||||
std::string frames = kAllFrames;
|
||||
double xscale = 1.0;
|
||||
double yscale = 1.0;
|
||||
bool applyPixelRatio = false;
|
||||
doc::AniDir aniDirValue = convert_string_to_anidir(m_aniDir);
|
||||
double scale = params().scale();
|
||||
doc::AniDir aniDirValue = params().aniDir();
|
||||
bool isForTwitter = false;
|
||||
|
||||
#if ENABLE_UI
|
||||
if (m_useUI && context->isUIAvailable()) {
|
||||
if (params().ui() && context->isUIAvailable()) {
|
||||
ExportFileWindow win(doc);
|
||||
bool askOverwrite = true;
|
||||
|
||||
@ -353,7 +359,9 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
std::string result =
|
||||
saveAsDialog(
|
||||
context, "Export",
|
||||
win.outputFilenameValue(), false, false,
|
||||
win.outputFilenameValue(),
|
||||
MarkAsSaved::Off,
|
||||
SaveInBackground::Off,
|
||||
(doc->isAssociatedToFile() ? doc->filename():
|
||||
std::string()));
|
||||
if (!result.empty())
|
||||
@ -362,6 +370,18 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
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();
|
||||
load_window_pos(&win, "ExportFile");
|
||||
again:;
|
||||
@ -389,31 +409,36 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
|
||||
layers = win.layersValue();
|
||||
frames = win.framesValue();
|
||||
xscale = yscale = win.resizeValue();
|
||||
scale = win.resizeValue();
|
||||
applyPixelRatio = win.applyPixelRatio();
|
||||
aniDirValue = win.aniDirValue();
|
||||
isForTwitter = win.isForTwitter();
|
||||
}
|
||||
#endif
|
||||
|
||||
gfx::PointF scaleXY(scale, scale);
|
||||
|
||||
// Pixel ratio
|
||||
if (applyPixelRatio) {
|
||||
doc::PixelRatio pr = doc->sprite()->pixelRatio();
|
||||
xscale *= pr.w;
|
||||
yscale *= pr.h;
|
||||
scaleXY.x *= pr.w;
|
||||
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;
|
||||
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());
|
||||
ASSERT(resizeCmd);
|
||||
if (resizeCmd) {
|
||||
int width = doc->sprite()->width();
|
||||
int height = doc->sprite()->height();
|
||||
int newWidth = int(double(width) * xscale);
|
||||
int newHeight = int(double(height) * yscale);
|
||||
int newWidth = int(double(width) * scaleXY.x);
|
||||
int newHeight = int(double(height) * scaleXY.y);
|
||||
if (newWidth < 1) newWidth = 1;
|
||||
if (newHeight < 1) newHeight = 1;
|
||||
if (width != newWidth || height != newHeight) {
|
||||
@ -441,27 +466,33 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
layers,
|
||||
layersVisibility);
|
||||
|
||||
// Selected frames to export
|
||||
SelectedFrames selFrames;
|
||||
Tag* tag = calculate_selected_frames(
|
||||
site, frames, selFrames);
|
||||
if (tag)
|
||||
m_tag = tag->name();
|
||||
m_selFrames = selFrames;
|
||||
// m_selFrames is not empty if fromFrame/toFrame parameters are
|
||||
// specified.
|
||||
if (m_selFrames.empty()) {
|
||||
// Selected frames to export
|
||||
SelectedFrames selFrames;
|
||||
Tag* tag = calculate_selected_frames(
|
||||
site, frames, selFrames);
|
||||
if (tag)
|
||||
params().tag(tag->name());
|
||||
m_selFrames = selFrames;
|
||||
}
|
||||
m_adjustFramesByTag = false;
|
||||
}
|
||||
|
||||
base::ScopedValue<std::string> restoreAniDir(
|
||||
m_aniDir,
|
||||
convert_anidir_to_string(aniDirValue), // New value
|
||||
m_aniDir); // Restore old value
|
||||
// Set ani dir
|
||||
params().aniDir(aniDirValue);
|
||||
|
||||
// TODO This should be set as options for the specific encoder
|
||||
GifEncoderDurationFix fixGif(isForTwitter);
|
||||
PngEncoderOneAlphaPixel fixPng(isForTwitter);
|
||||
|
||||
saveDocumentInBackground(
|
||||
context, doc, outputFilename, false);
|
||||
context, doc, outputFilename,
|
||||
MarkAsSaved::Off,
|
||||
(resizeOnTheFly ? ResizeOnTheFly::On:
|
||||
ResizeOnTheFly::Off),
|
||||
scaleXY);
|
||||
}
|
||||
|
||||
// Undo resize
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2021-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,15 +10,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/new_params.h"
|
||||
#include "doc/anidir.h"
|
||||
#include "doc/selected_frames.h"
|
||||
#include "gfx/point.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
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:
|
||||
enum class MarkAsSaved { Off, On };
|
||||
enum class SaveInBackground { Off, On };
|
||||
enum class ResizeOnTheFly { Off, On };
|
||||
|
||||
SaveFileBaseCommand(const char* id, CommandFlags flags);
|
||||
|
||||
protected:
|
||||
@ -28,24 +49,19 @@ namespace app {
|
||||
Context* context,
|
||||
const std::string& dlgTitle,
|
||||
const std::string& filename,
|
||||
const bool markAsSaved,
|
||||
const bool saveInBackground = true,
|
||||
const MarkAsSaved markAsSaved,
|
||||
const SaveInBackground saveInBackground = SaveInBackground::On,
|
||||
const std::string& forbiddenFilename = std::string());
|
||||
void saveDocumentInBackground(
|
||||
const Context* context,
|
||||
Doc* document,
|
||||
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;
|
||||
bool m_adjustFramesByTag;
|
||||
bool m_useUI;
|
||||
bool m_ignoreEmpty;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -18,6 +18,7 @@
|
||||
#include "base/split_string.h"
|
||||
#include "base/string.h"
|
||||
#include "doc/algorithm/resize_image.h"
|
||||
#include "doc/anidir.h"
|
||||
#include "doc/color_mode.h"
|
||||
#include "doc/rgbmap_algorithm.h"
|
||||
#include "filters/color_curve.h"
|
||||
@ -145,6 +146,12 @@ void Param<doc::ColorMode>::fromString(const std::string& value)
|
||||
setValue(doc::ColorMode::RGB);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Param<doc::AniDir>::fromString(const std::string& value)
|
||||
{
|
||||
setValue(convert_string_to_anidir(value));
|
||||
}
|
||||
|
||||
template<>
|
||||
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));
|
||||
}
|
||||
|
||||
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<>
|
||||
void Param<app::Color>::fromLua(lua_State* L, int index)
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -67,7 +67,8 @@ class BmpFormat : public FileFormat {
|
||||
FILE_SUPPORT_RGB |
|
||||
FILE_SUPPORT_GRAY |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES;
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -688,34 +689,34 @@ bool BmpFormat::onLoad(FileOp *fop)
|
||||
else
|
||||
rmask = gmask = bmask = 0;
|
||||
|
||||
Image* image = fop->sequenceImage(pixelFormat,
|
||||
infoheader.biWidth,
|
||||
ABS((int)infoheader.biHeight));
|
||||
ImageRef image = fop->sequenceImage(pixelFormat,
|
||||
infoheader.biWidth,
|
||||
ABS((int)infoheader.biHeight));
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pixelFormat == IMAGE_RGB)
|
||||
clear_image(image, rgba(0, 0, 0, 255));
|
||||
clear_image(image.get(), rgba(0, 0, 0, 255));
|
||||
else
|
||||
clear_image(image, 0);
|
||||
clear_image(image.get(), 0);
|
||||
|
||||
switch (infoheader.biCompression) {
|
||||
|
||||
case BI_RGB:
|
||||
read_image(f, image, &infoheader, fop);
|
||||
read_image(f, image.get(), &infoheader, fop);
|
||||
break;
|
||||
|
||||
case BI_RLE8:
|
||||
read_rle8_compressed_image(f, image, &infoheader);
|
||||
read_rle8_compressed_image(f, image.get(), &infoheader);
|
||||
break;
|
||||
|
||||
case BI_RLE4:
|
||||
read_rle4_compressed_image(f, image, &infoheader);
|
||||
read_rle4_compressed_image(f, image.get(), &infoheader);
|
||||
break;
|
||||
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
@ -751,21 +752,22 @@ bool BmpFormat::onLoad(FileOp *fop)
|
||||
#ifdef ENABLE_SAVE
|
||||
bool BmpFormat::onSave(FileOp *fop)
|
||||
{
|
||||
const Image* image = fop->sequenceImage();
|
||||
const int w = image->width();
|
||||
const int h = image->height();
|
||||
const FileAbstractImage* img = fop->abstractImage();
|
||||
const ImageSpec spec = img->spec();
|
||||
const int w = spec.width();
|
||||
const int h = spec.height();
|
||||
int bfSize;
|
||||
int biSizeImage;
|
||||
int ncolors = fop->sequenceGetNColors();
|
||||
int bpp = 0;
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
switch (spec.colorMode()) {
|
||||
case ColorMode::RGB:
|
||||
bpp = 24;
|
||||
break;
|
||||
case IMAGE_GRAYSCALE:
|
||||
case ColorMode::GRAYSCALE:
|
||||
bpp = 8;
|
||||
break;
|
||||
case IMAGE_INDEXED: {
|
||||
case ColorMode::INDEXED: {
|
||||
if (ncolors > 16)
|
||||
bpp = 8;
|
||||
else if (ncolors > 2)
|
||||
@ -776,7 +778,7 @@ bool BmpFormat::onSave(FileOp *fop)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// TODO save IMAGE_BITMAP as 1bpp bmp?
|
||||
// TODO save ColorMode::BITMAP as 1bpp bmp?
|
||||
// Invalid image format
|
||||
fop->setError("Unsupported color mode.\n");
|
||||
return false;
|
||||
@ -851,31 +853,37 @@ bool BmpFormat::onSave(FileOp *fop)
|
||||
|
||||
// Save image pixels (from bottom to top)
|
||||
for (i=h-1; i>=0; i--) {
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
switch (spec.colorMode()) {
|
||||
case ColorMode::RGB: {
|
||||
auto scanline = (const uint32_t*)img->getScanline(i);
|
||||
for (j=0; j<w; ++j) {
|
||||
c = get_pixel_fast<RgbTraits>(image, j, i);
|
||||
c = scanline[j];
|
||||
fputc(rgba_getb(c), f);
|
||||
fputc(rgba_getg(c), f);
|
||||
fputc(rgba_getr(c), f);
|
||||
}
|
||||
break;
|
||||
case IMAGE_GRAYSCALE:
|
||||
}
|
||||
case ColorMode::GRAYSCALE: {
|
||||
auto scanline = (const uint16_t*)img->getScanline(i);
|
||||
for (j=0; j<w; ++j) {
|
||||
c = get_pixel_fast<GrayscaleTraits>(image, j, i);
|
||||
c = scanline[j];
|
||||
fputc(graya_getv(c), f);
|
||||
}
|
||||
break;
|
||||
case IMAGE_INDEXED:
|
||||
}
|
||||
case ColorMode::INDEXED: {
|
||||
auto scanline = (const uint8_t*)img->getScanline(i);
|
||||
for (j=0; j<w; ) {
|
||||
uint8_t value = 0;
|
||||
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);
|
||||
}
|
||||
fputc(value, f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (j=0; j<filler; j++)
|
||||
|
@ -88,7 +88,7 @@ bool CssFormat::onLoad(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;
|
||||
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
@ -160,7 +160,7 @@ bool CssFormat::onSave(FileOp* fop)
|
||||
case IMAGE_RGB: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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);
|
||||
if (alpha != 0x00) {
|
||||
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: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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);
|
||||
alpha = graya_geta(c);
|
||||
if (alpha != 0x00) {
|
||||
@ -204,7 +204,7 @@ bool CssFormat::onSave(FileOp* fop)
|
||||
}
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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 (css_options->withVars) {
|
||||
print_shadow_index(x, y, c, num_printed_pixels>0);
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "base/scoped_lock.h"
|
||||
#include "base/string.h"
|
||||
#include "dio/detect_format.h"
|
||||
#include "doc/algorithm/resize_image.h"
|
||||
#include "doc/doc.h"
|
||||
#include "fmt/format.h"
|
||||
#include "render/quantization.h"
|
||||
@ -53,6 +54,130 @@ namespace app {
|
||||
|
||||
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 paths;
|
||||
@ -586,6 +711,18 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
|
||||
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.
|
||||
//
|
||||
// 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())
|
||||
continue; // Skip frame because there is no slice key
|
||||
|
||||
if (m_abstractImage)
|
||||
m_abstractImage->setSliceBounds(key->bounds());
|
||||
|
||||
m_seq.image.reset(
|
||||
Image::create(sprite->pixelFormat(),
|
||||
key->bounds().w,
|
||||
@ -1082,7 +1222,7 @@ void FileOp::sequenceGetAlpha(int index, int* a) const
|
||||
*a = 0;
|
||||
}
|
||||
|
||||
Image* FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
|
||||
ImageRef FileOp::sequenceImage(PixelFormat pixelFormat, int w, int h)
|
||||
{
|
||||
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.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, ...)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -20,6 +20,7 @@
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/pixel_format.h"
|
||||
#include "doc/selected_frames.h"
|
||||
#include "os/color_space.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
@ -94,6 +95,33 @@ namespace app {
|
||||
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.
|
||||
//
|
||||
// TODO This class do to many things. There should be a previous
|
||||
@ -113,6 +141,8 @@ namespace app {
|
||||
const std::string& filenameFormat,
|
||||
const bool ignoreEmptyFrames);
|
||||
|
||||
static bool checkIfFormatSupportResizeOnTheFly(const std::string& filename);
|
||||
|
||||
~FileOp();
|
||||
|
||||
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 sequenceSetAlpha(int index, int a);
|
||||
void sequenceGetAlpha(int index, int* a) const;
|
||||
Image* sequenceImage(PixelFormat pixelFormat, int w, int h);
|
||||
const Image* sequenceImage() const { return m_seq.image.get(); }
|
||||
ImageRef sequenceImage(PixelFormat pixelFormat, int w, int h);
|
||||
const ImageRef sequenceImage() const { return m_seq.image; }
|
||||
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
||||
bool sequenceGetHasAlpha() const {
|
||||
return m_seq.has_alpha;
|
||||
@ -202,6 +232,11 @@ namespace app {
|
||||
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; }
|
||||
void setError(const char *error, ...);
|
||||
bool hasError() const { return !m_error.empty(); }
|
||||
@ -276,7 +311,11 @@ namespace app {
|
||||
int flags;
|
||||
} m_seq;
|
||||
|
||||
class FileAbstractImageImpl;
|
||||
std::unique_ptr<FileAbstractImageImpl> m_abstractImage;
|
||||
|
||||
void prepareForSequence();
|
||||
void makeAbstractImage();
|
||||
};
|
||||
|
||||
// Available extensions for each load/save operation.
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define FILE_SUPPORT_TAGS 0x00001000
|
||||
#define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors
|
||||
#define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000
|
||||
#define FILE_ENCODE_ABSTRACT_IMAGE 0x00008000 // Use the new FileAbstractImage
|
||||
|
||||
namespace app {
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -93,7 +93,8 @@ class GifFormat : public FileFormat {
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_FRAMES |
|
||||
FILE_SUPPORT_PALETTES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -954,10 +955,11 @@ public:
|
||||
GifEncoder(FileOp* fop, GifFileType* gifFile)
|
||||
: m_fop(fop)
|
||||
, m_gifFile(gifFile)
|
||||
, m_document(fop->document())
|
||||
, m_sprite(fop->document()->sprite())
|
||||
, m_spriteBounds(m_sprite->bounds())
|
||||
, m_hasBackground(m_sprite->isOpaque())
|
||||
, m_img(fop->abstractImage())
|
||||
, m_spec(m_img->spec())
|
||||
, m_spriteBounds(m_spec.bounds())
|
||||
, m_hasBackground(m_img->isOpaque())
|
||||
, m_bitsPerPixel(1)
|
||||
, m_globalColormap(nullptr)
|
||||
, m_globalColormapPalette(*m_sprite->palette(0))
|
||||
@ -973,7 +975,7 @@ public:
|
||||
m_lastFrameBounds = m_spriteBounds;
|
||||
m_lastDisposal = DisposalMethod::NONE;
|
||||
|
||||
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
|
||||
if (m_spec.colorMode() == ColorMode::INDEXED) {
|
||||
for (Palette* palette : m_sprite->getPalettes()) {
|
||||
int bpp = GifBitSizeLimited(palette->size());
|
||||
m_bitsPerPixel = std::max(m_bitsPerPixel, bpp);
|
||||
@ -983,8 +985,8 @@ public:
|
||||
m_bitsPerPixel = 8;
|
||||
}
|
||||
|
||||
if (m_sprite->pixelFormat() == IMAGE_INDEXED &&
|
||||
m_sprite->getPalettes().size() == 1) {
|
||||
if (m_spec.colorMode() == ColorMode::INDEXED &&
|
||||
m_img->palettes().size() == 1) {
|
||||
// If some layer has opacity < 255 or a different blend mode, we
|
||||
// need to create color palettes.
|
||||
bool quantizeColormaps = false;
|
||||
@ -1001,7 +1003,7 @@ public:
|
||||
|
||||
if (!quantizeColormaps) {
|
||||
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
|
||||
// palette order without lossing compression rate.
|
||||
if (m_hasBackground)
|
||||
@ -1025,7 +1027,7 @@ public:
|
||||
m_transparentIndex = (m_hasBackground ? -1: m_bgIndex);
|
||||
if (m_globalColormap) {
|
||||
// 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);
|
||||
bool maskColorFounded = false;
|
||||
@ -1331,7 +1333,7 @@ private:
|
||||
const DisposalMethod disposalMethod,
|
||||
const bool fixDuration) {
|
||||
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
|
||||
// 1/4 of its duration for some strange reason in the Twitter
|
||||
@ -1550,15 +1552,11 @@ private:
|
||||
}
|
||||
|
||||
void renderFrame(frame_t frame, Image* dst) {
|
||||
render::Render render;
|
||||
render.setNewBlend(m_fop->newBlend());
|
||||
|
||||
render.setBgType(render::BgType::NONE);
|
||||
if (m_preservePaletteOrder)
|
||||
clear_image(dst, m_bgIndex);
|
||||
else
|
||||
clear_image(dst, 0);
|
||||
render.renderSprite(dst, m_sprite, frame);
|
||||
m_img->renderFrame(frame, dst);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -1568,8 +1566,7 @@ private:
|
||||
ColorMapObject* colormap = GifMakeMapObject(n, nullptr);
|
||||
|
||||
// Color space conversions
|
||||
ConvertCS convert = convert_from_custom_to_srgb(
|
||||
m_document->osColorSpace());
|
||||
ConvertCS convert = convert_from_custom_to_srgb(m_img->osColorSpace());
|
||||
|
||||
for (int i=0; i<n; ++i) {
|
||||
color_t color;
|
||||
@ -1590,8 +1587,9 @@ private:
|
||||
|
||||
FileOp* m_fop;
|
||||
GifFileType* m_gifFile;
|
||||
const Doc* m_document;
|
||||
const Sprite* m_sprite;
|
||||
const FileAbstractImage* m_img;
|
||||
const ImageSpec m_spec;
|
||||
gfx::Rect m_spriteBounds;
|
||||
bool m_hasBackground;
|
||||
int m_bgIndex;
|
||||
|
@ -64,7 +64,8 @@ class JpegFormat : public FileFormat {
|
||||
FILE_SUPPORT_RGB |
|
||||
FILE_SUPPORT_GRAY |
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -177,7 +178,7 @@ bool JpegFormat::onLoad(FileOp* fop)
|
||||
jpeg_start_decompress(&dinfo);
|
||||
|
||||
// Create the image.
|
||||
Image* image = fop->sequenceImage(
|
||||
ImageRef image = fop->sequenceImage(
|
||||
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
|
||||
IMAGE_GRAYSCALE),
|
||||
dinfo.output_width,
|
||||
@ -352,7 +353,8 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||
{
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct error_mgr jerr;
|
||||
const Image* image = fop->sequenceImage();
|
||||
const FileAbstractImage* img = fop->abstractImage();
|
||||
const ImageSpec spec = img->spec();
|
||||
JSAMPARRAY buffer;
|
||||
JDIMENSION buffer_height;
|
||||
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);
|
||||
|
||||
// SET parameters for compression.
|
||||
cinfo.image_width = image->width();
|
||||
cinfo.image_height = image->height();
|
||||
cinfo.image_width = spec.width();
|
||||
cinfo.image_height = spec.height();
|
||||
|
||||
if (image->pixelFormat() == IMAGE_GRAYSCALE) {
|
||||
if (spec.colorMode() == ColorMode::GRAYSCALE) {
|
||||
cinfo.input_components = 1;
|
||||
cinfo.in_color_space = JCS_GRAYSCALE;
|
||||
}
|
||||
@ -427,15 +429,15 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||
// Write each scan line.
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
// RGB
|
||||
if (image->pixelFormat() == IMAGE_RGB) {
|
||||
if (spec.colorMode() == ColorMode::RGB) {
|
||||
uint32_t* src_address;
|
||||
uint8_t* dst_address;
|
||||
int x, 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];
|
||||
|
||||
for (x=0; x<image->width(); ++x) {
|
||||
for (x=0; x<spec.width(); ++x) {
|
||||
c = *(src_address++);
|
||||
*(dst_address++) = rgba_getr(c);
|
||||
*(dst_address++) = rgba_getg(c);
|
||||
@ -449,9 +451,9 @@ bool JpegFormat::onSave(FileOp* fop)
|
||||
uint8_t* dst_address;
|
||||
int x, 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];
|
||||
for (x=0; x<image->width(); ++x)
|
||||
for (x=0; x<spec.width(); ++x)
|
||||
*(dst_address++) = graya_getv(*(src_address++));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -43,7 +44,8 @@ class PcxFormat : public FileFormat {
|
||||
FILE_SUPPORT_RGB |
|
||||
FILE_SUPPORT_GRAY |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES;
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -104,16 +106,16 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||
for (c=0; c<60; c++) /* skip some more junk */
|
||||
fgetc(f);
|
||||
|
||||
Image* image = fop->sequenceImage(bpp == 8 ?
|
||||
IMAGE_INDEXED:
|
||||
IMAGE_RGB,
|
||||
width, height);
|
||||
ImageRef image = fop->sequenceImage(bpp == 8 ?
|
||||
IMAGE_INDEXED:
|
||||
IMAGE_RGB,
|
||||
width, height);
|
||||
if (!image) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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 */
|
||||
x = xx = 0;
|
||||
@ -131,7 +133,7 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||
if (bpp == 8) {
|
||||
while (c--) {
|
||||
if (x < image->width())
|
||||
put_pixel_fast<IndexedTraits>(image, x, y, ch);
|
||||
put_pixel_fast<IndexedTraits>(image.get(), x, y, ch);
|
||||
|
||||
x++;
|
||||
}
|
||||
@ -139,8 +141,8 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||
else {
|
||||
while (c--) {
|
||||
if (xx < image->width())
|
||||
put_pixel_fast<RgbTraits>(image, xx, y,
|
||||
get_pixel_fast<RgbTraits>(image, xx, y) | ((ch & 0xff) << po));
|
||||
put_pixel_fast<RgbTraits>(image.get(), xx, y,
|
||||
get_pixel_fast<RgbTraits>(image.get(), xx, y) | ((ch & 0xff) << po));
|
||||
|
||||
x++;
|
||||
if (x == bytes_per_line) {
|
||||
@ -190,7 +192,8 @@ bool PcxFormat::onLoad(FileOp* fop)
|
||||
#ifdef ENABLE_SAVE
|
||||
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 x, y;
|
||||
int runcount;
|
||||
@ -201,7 +204,7 @@ bool PcxFormat::onSave(FileOp* fop)
|
||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||
FILE* f = handle.get();
|
||||
|
||||
if (image->pixelFormat() == IMAGE_RGB) {
|
||||
if (spec.colorMode() == ColorMode::RGB) {
|
||||
depth = 24;
|
||||
planes = 3;
|
||||
}
|
||||
@ -216,8 +219,8 @@ bool PcxFormat::onSave(FileOp* fop)
|
||||
fputc(8, f); /* 8 bits per pixel */
|
||||
fputw(0, f); /* xmin */
|
||||
fputw(0, f); /* ymin */
|
||||
fputw(image->width()-1, f); /* xmax */
|
||||
fputw(image->height()-1, f); /* ymax */
|
||||
fputw(spec.width()-1, f); /* xmax */
|
||||
fputw(spec.height()-1, f); /* ymax */
|
||||
fputw(320, f); /* HDpi */
|
||||
fputw(200, f); /* VDpi */
|
||||
|
||||
@ -230,36 +233,39 @@ bool PcxFormat::onSave(FileOp* fop)
|
||||
|
||||
fputc(0, f); /* reserved */
|
||||
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(image->width(), f); /* hscreen size */
|
||||
fputw(image->height(), f); /* vscreen size */
|
||||
fputw(spec.width(), f); /* hscreen size */
|
||||
fputw(spec.height(), f); /* vscreen size */
|
||||
for (c=0; c<54; c++) /* filler */
|
||||
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;
|
||||
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 (image->pixelFormat() == IMAGE_INDEXED)
|
||||
ch = get_pixel_fast<IndexedTraits>(image, x, y);
|
||||
else if (image->pixelFormat() == IMAGE_GRAYSCALE) {
|
||||
c = get_pixel_fast<GrayscaleTraits>(image, x, y);
|
||||
if (spec.colorMode() == ColorMode::INDEXED)
|
||||
ch = scanline[x];
|
||||
else if (spec.colorMode() == ColorMode::GRAYSCALE) {
|
||||
c = ((const uint16_t*)scanline)[x];
|
||||
ch = graya_getv(c);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (x < image->width()) {
|
||||
c = get_pixel_fast<RgbTraits>(image, x, y);
|
||||
if (x < spec.width()) {
|
||||
c = ((const uint32_t*)scanline)[x];
|
||||
ch = rgba_getr(c);
|
||||
}
|
||||
else if (x<image->width()*2) {
|
||||
c = get_pixel_fast<RgbTraits>(image, x-image->width(), y);
|
||||
else if (x<spec.width()*2) {
|
||||
c = ((const uint32_t*)scanline)[x-spec.width()];
|
||||
ch = rgba_getg(c);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -285,7 +291,7 @@ bool PcxFormat::onSave(FileOp* fop)
|
||||
|
||||
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 */
|
||||
|
@ -54,7 +54,8 @@ class PngFormat : public FileFormat {
|
||||
FILE_SUPPORT_GRAYA |
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_SUPPORT_PALETTE_WITH_ALPHA;
|
||||
FILE_SUPPORT_PALETTE_WITH_ALPHA |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -275,7 +276,7 @@ bool PngFormat::onLoad(FileOp* fop)
|
||||
|
||||
int imageWidth = png_get_image_width(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) {
|
||||
fop->setError("file_sequence_image %dx%d\n", imageWidth, imageHeight);
|
||||
return false;
|
||||
@ -550,23 +551,25 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
|
||||
png_init_io(png, fp);
|
||||
|
||||
const Image* image = fop->sequenceImage();
|
||||
switch (image->pixelFormat()) {
|
||||
case IMAGE_RGB:
|
||||
const FileAbstractImage* img = fop->abstractImage();
|
||||
const ImageSpec spec = img->spec();
|
||||
|
||||
switch (spec.colorMode()) {
|
||||
case ColorMode::RGB:
|
||||
color_type =
|
||||
(fop->document()->sprite()->needAlpha() ||
|
||||
(img->needAlpha() ||
|
||||
fix_one_alpha_pixel ?
|
||||
PNG_COLOR_TYPE_RGB_ALPHA:
|
||||
PNG_COLOR_TYPE_RGB);
|
||||
break;
|
||||
case IMAGE_GRAYSCALE:
|
||||
case ColorMode::GRAYSCALE:
|
||||
color_type =
|
||||
(fop->document()->sprite()->needAlpha() ||
|
||||
(img->needAlpha() ||
|
||||
fix_one_alpha_pixel ?
|
||||
PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||
PNG_COLOR_TYPE_GRAY);
|
||||
break;
|
||||
case IMAGE_INDEXED:
|
||||
case ColorMode::INDEXED:
|
||||
if (fix_one_alpha_pixel)
|
||||
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
else
|
||||
@ -574,8 +577,8 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
break;
|
||||
}
|
||||
|
||||
const png_uint_32 width = image->width();
|
||||
const png_uint_32 height = image->height();
|
||||
const png_uint_32 width = spec.width();
|
||||
const png_uint_32 height = spec.height();
|
||||
|
||||
png_set_IHDR(png, info, width, height, 8, color_type,
|
||||
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);
|
||||
}
|
||||
|
||||
if (fop->preserveColorProfile() &&
|
||||
fop->document()->sprite()->colorSpace())
|
||||
saveColorSpace(png, info, fop->document()->sprite()->colorSpace().get());
|
||||
if (fop->preserveColorProfile() && spec.colorSpace()) {
|
||||
saveColorSpace(png, info, spec.colorSpace().get());
|
||||
}
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||
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
|
||||
// put alpha=0 to the transparent color.
|
||||
int mask_entry = -1;
|
||||
if (fop->document()->sprite()->backgroundLayer() == NULL ||
|
||||
if (fop->document()->sprite()->backgroundLayer() == nullptr ||
|
||||
!fop->document()->sprite()->backgroundLayer()->isVisible()) {
|
||||
mask_entry = fop->document()->sprite()->transparentColor();
|
||||
}
|
||||
@ -668,8 +671,8 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
unsigned int x, c, a;
|
||||
bool opaque = true;
|
||||
|
||||
if (image->pixelFormat() == IMAGE_RGB) {
|
||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
||||
if (spec.colorMode() == ColorMode::RGB) {
|
||||
auto src_address = (const uint32_t*)img->getScanline(y);
|
||||
|
||||
for (x=0; x<width; ++x) {
|
||||
c = *(src_address++);
|
||||
@ -690,8 +693,8 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
}
|
||||
// In case that we are converting an indexed image to RGB just
|
||||
// to convert one pixel with alpha=254.
|
||||
else if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
||||
else if (spec.colorMode() == ColorMode::INDEXED) {
|
||||
auto src_address = (const uint8_t*)img->getScanline(y);
|
||||
unsigned int x, c;
|
||||
int r, g, b, a;
|
||||
bool opaque = true;
|
||||
@ -716,7 +719,7 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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) {
|
||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
||||
auto src_address = (const uint16_t*)img->getScanline(y);
|
||||
unsigned int x, c, a;
|
||||
bool opaque = true;
|
||||
|
||||
@ -747,7 +750,7 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
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) {
|
||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
||||
auto src_address = (const uint8_t*)img->getScanline(y);
|
||||
unsigned int x;
|
||||
|
||||
for (x=0; x<width; ++x)
|
||||
@ -771,7 +774,7 @@ bool PngFormat::onSave(FileOp* fop)
|
||||
png_free(png, row_pointer);
|
||||
png_write_end(png, info);
|
||||
|
||||
if (image->pixelFormat() == IMAGE_INDEXED) {
|
||||
if (spec.colorMode() == ColorMode::INDEXED) {
|
||||
png_free(png, palette);
|
||||
palette = nullptr;
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ bool SvgFormat::onLoad(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;
|
||||
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
||||
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
|
||||
@ -103,7 +103,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
||||
case IMAGE_RGB: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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);
|
||||
if (alpha != 0x00)
|
||||
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: {
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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);
|
||||
alpha = graya_geta(c);
|
||||
if (alpha != 0x00)
|
||||
@ -142,7 +142,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
||||
}
|
||||
for (y=0; y<image->height(); y++) {
|
||||
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)
|
||||
printcol(x, y, image_palette[c][0] & 0xff,
|
||||
image_palette[c][1] & 0xff,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -53,7 +53,8 @@ class TgaFormat : public FileFormat {
|
||||
FILE_SUPPORT_INDEXED |
|
||||
FILE_SUPPORT_SEQUENCES |
|
||||
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||
FILE_SUPPORT_PALETTE_WITH_ALPHA;
|
||||
FILE_SUPPORT_PALETTE_WITH_ALPHA |
|
||||
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||
}
|
||||
|
||||
bool onLoad(FileOp* fop) override;
|
||||
@ -164,9 +165,9 @@ bool TgaFormat::onLoad(FileOp* fop)
|
||||
if (decoder.hasAlpha())
|
||||
fop->sequenceSetHasAlpha(true);
|
||||
|
||||
Image* image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
|
||||
spec.width(),
|
||||
spec.height());
|
||||
ImageRef image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
|
||||
spec.width(),
|
||||
spec.height());
|
||||
if (!image)
|
||||
return false;
|
||||
|
||||
@ -188,7 +189,7 @@ bool TgaFormat::onLoad(FileOp* fop)
|
||||
// Post process gray image pixels (because we use grayscale images
|
||||
// with alpha).
|
||||
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) {
|
||||
*it = doc::graya(*it, 255);
|
||||
}
|
||||
@ -217,7 +218,7 @@ bool TgaFormat::onLoad(FileOp* fop)
|
||||
namespace {
|
||||
|
||||
void prepare_header(tga::Header& header,
|
||||
const doc::Image* image,
|
||||
const doc::ImageSpec& spec,
|
||||
const doc::Palette* palette,
|
||||
const bool isOpaque,
|
||||
const bool compressed,
|
||||
@ -231,13 +232,13 @@ void prepare_header(tga::Header& header,
|
||||
header.colormapDepth = 0;
|
||||
header.xOrigin = 0;
|
||||
header.yOrigin = 0;
|
||||
header.width = image->width();
|
||||
header.height = image->height();
|
||||
header.width = spec.width();
|
||||
header.height = spec.height();
|
||||
header.bitsPerPixel = 0;
|
||||
// TODO make this option configurable
|
||||
header.imageDescriptor = 0x20; // Top-to-bottom
|
||||
|
||||
switch (image->colorMode()) {
|
||||
switch (spec.colorMode()) {
|
||||
case ColorMode::RGB:
|
||||
header.imageType = (compressed ? tga::RleRgb: tga::UncompressedRgb);
|
||||
header.bitsPerPixel = (bitsPerPixel > 8 ?
|
||||
@ -287,7 +288,7 @@ void prepare_header(tga::Header& header,
|
||||
|
||||
bool TgaFormat::onSave(FileOp* fop)
|
||||
{
|
||||
const Image* image = fop->sequenceImage();
|
||||
const FileAbstractImage* img = fop->abstractImage();
|
||||
const Palette* palette = fop->sequenceGetPalette();
|
||||
|
||||
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());
|
||||
prepare_header(
|
||||
header, image, palette,
|
||||
header, img->spec(), palette,
|
||||
// Is alpha channel required?
|
||||
fop->document()->sprite()->isOpaque(),
|
||||
// Compressed by default
|
||||
@ -307,6 +308,7 @@ bool TgaFormat::onSave(FileOp* fop)
|
||||
|
||||
encoder.writeHeader(header);
|
||||
|
||||
doc::ImageRef image = img->getScaledImage();
|
||||
tga::Image tgaImage;
|
||||
tgaImage.pixels = image->getPixelAddress(0, 0);
|
||||
tgaImage.rowstride = image->getRowStrideSize();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -112,7 +112,10 @@ void Preferences::load()
|
||||
|
||||
void Preferences::save()
|
||||
{
|
||||
ui::assert_ui_thread();
|
||||
#ifdef _DEBUG
|
||||
if (ui::UISystem::instance())
|
||||
ui::assert_ui_thread();
|
||||
#endif
|
||||
app::gen::GlobalPref::save();
|
||||
|
||||
for (auto& pair : m_tools)
|
||||
|
@ -10,6 +10,6 @@
|
||||
|
||||
// Increment this value if the scripting API is modified between two
|
||||
// released Aseprite versions.
|
||||
#define API_VERSION 18
|
||||
#define API_VERSION 19
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2015-2018 David Capello
|
||||
//
|
||||
// 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)) {
|
||||
spec = *spec2;
|
||||
}
|
||||
else if (may_get_obj<ImageObj>(L, 1)) {
|
||||
return Image_clone(L);
|
||||
else if (auto imgObj = may_get_obj<ImageObj>(L, 1)) {
|
||||
// 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)) {
|
||||
image = doc::Image::create(spr->spec());
|
||||
|
@ -35,6 +35,7 @@ namespace app {
|
||||
AppMenuItem(const std::string& text,
|
||||
const std::string& commandId = std::string(),
|
||||
const Params& params = Params());
|
||||
AppMenuItem(const std::string& text, std::nullptr_t) = delete;
|
||||
|
||||
KeyPtr key() { return m_key; }
|
||||
void setKey(const KeyPtr& key);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -51,8 +51,7 @@ ExportFileWindow::ExportFileWindow(const Doc* doc)
|
||||
}
|
||||
|
||||
// Default export configuration
|
||||
resize()->setValue(
|
||||
base::convert_to<std::string>(m_docPref.saveCopy.resizeScale()));
|
||||
setResizeScale(m_docPref.saveCopy.resizeScale());
|
||||
fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer());
|
||||
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
|
||||
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
|
||||
@ -113,7 +112,8 @@ std::string ExportFileWindow::outputFilenameValue() 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
|
||||
@ -141,6 +141,16 @@ bool ExportFileWindow::isForTwitter() const
|
||||
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)
|
||||
{
|
||||
m_outputPath = base::get_file_path(pathAndFilename);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -34,10 +34,13 @@ namespace app {
|
||||
bool applyPixelRatio() 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;
|
||||
|
||||
private:
|
||||
void setOutputFilename(const std::string& pathAndFilename);
|
||||
void updateOutputFilenameEntry();
|
||||
void onOutputFilenameEntryChange();
|
||||
void updateAniDir();
|
||||
|
@ -267,6 +267,11 @@ void ExpandCelCanvas::commit()
|
||||
// And add the cel again in the layer.
|
||||
m_cmds->executeAndAdd(new cmd::AddCel(m_layer, m_cel));
|
||||
}
|
||||
else {
|
||||
// Delete unused cel
|
||||
delete m_cel;
|
||||
m_cel = nullptr;
|
||||
}
|
||||
}
|
||||
// We are selecting...
|
||||
else {
|
||||
|
@ -231,6 +231,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
|
||||
bool editable = bool_attr(elem, "editable", false);
|
||||
if (editable)
|
||||
((ComboBox*)widget)->setEditable(true);
|
||||
|
||||
const char* suffix = elem->Attribute("suffix");
|
||||
if (suffix)
|
||||
((ComboBox*)widget)->getEntryWidget()->setSuffix(suffix);
|
||||
}
|
||||
else if (elem_name == "entry" ||
|
||||
elem_name == "expr") {
|
||||
|
@ -328,7 +328,8 @@ void ComboBox::setValue(const std::string& value)
|
||||
{
|
||||
if (isEditable()) {
|
||||
m_entry->setText(value);
|
||||
m_entry->selectAllText();
|
||||
if (hasFocus())
|
||||
m_entry->selectAllText();
|
||||
}
|
||||
else {
|
||||
int index = findItemIndexByValue(value);
|
||||
@ -560,6 +561,14 @@ bool ComboBoxEntry::onProcessMessage(Message* msg)
|
||||
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);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// 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->setImmutable();
|
||||
|
||||
m_captured = displaySurface;
|
||||
m_captured = base::AddRef(displaySurface);
|
||||
}
|
||||
|
||||
void Overlay::restoreOverlappedArea(const gfx::Rect& restoreBounds)
|
||||
@ -118,7 +118,7 @@ void Overlay::restoreOverlappedArea(const gfx::Rect& restoreBounds)
|
||||
return;
|
||||
|
||||
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_display->dirtyRect(bounds());
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// 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)
|
||||
// region. It's nullptr if the overlay wasn't drawn yet.
|
||||
os::Surface* m_captured;
|
||||
os::SurfaceRef m_captured;
|
||||
|
||||
gfx::Point m_pos;
|
||||
ZOrder m_zorder;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
|
Loading…
x
Reference in New Issue
Block a user