mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-11 09:40:42 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
caf475b2dc
@ -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" />
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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++)
|
||||||
|
@ -88,7 +88,7 @@ bool CssFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
bool CssFormat::onSave(FileOp* fop)
|
bool CssFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const Image* image = fop->sequenceImage();
|
const ImageRef image = fop->sequenceImage();
|
||||||
int x, y, c, r, g, b, a, alpha;
|
int x, y, c, r, g, b, a, alpha;
|
||||||
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
const auto css_options = std::static_pointer_cast<CssOptions>(fop->formatOptions());
|
||||||
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
|
||||||
@ -160,7 +160,7 @@ bool CssFormat::onSave(FileOp* fop)
|
|||||||
case IMAGE_RGB: {
|
case IMAGE_RGB: {
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<RgbTraits>(image, x, y);
|
c = get_pixel_fast<RgbTraits>(image.get(), x, y);
|
||||||
alpha = rgba_geta(c);
|
alpha = rgba_geta(c);
|
||||||
if (alpha != 0x00) {
|
if (alpha != 0x00) {
|
||||||
print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c),
|
print_shadow_color(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c),
|
||||||
@ -175,7 +175,7 @@ bool CssFormat::onSave(FileOp* fop)
|
|||||||
case IMAGE_GRAYSCALE: {
|
case IMAGE_GRAYSCALE: {
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<GrayscaleTraits>(image, x, y);
|
c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y);
|
||||||
auto v = graya_getv(c);
|
auto v = graya_getv(c);
|
||||||
alpha = graya_geta(c);
|
alpha = graya_geta(c);
|
||||||
if (alpha != 0x00) {
|
if (alpha != 0x00) {
|
||||||
@ -204,7 +204,7 @@ bool CssFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<IndexedTraits>(image, x, y);
|
c = get_pixel_fast<IndexedTraits>(image.get(), x, y);
|
||||||
if (c != mask_color) {
|
if (c != mask_color) {
|
||||||
if (css_options->withVars) {
|
if (css_options->withVars) {
|
||||||
print_shadow_index(x, y, c, num_printed_pixels>0);
|
print_shadow_index(x, y, c, num_printed_pixels>0);
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
#include "base/scoped_lock.h"
|
#include "base/scoped_lock.h"
|
||||||
#include "base/string.h"
|
#include "base/string.h"
|
||||||
#include "dio/detect_format.h"
|
#include "dio/detect_format.h"
|
||||||
|
#include "doc/algorithm/resize_image.h"
|
||||||
#include "doc/doc.h"
|
#include "doc/doc.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "render/quantization.h"
|
#include "render/quantization.h"
|
||||||
@ -53,6 +54,130 @@ namespace app {
|
|||||||
|
|
||||||
using namespace base;
|
using namespace base;
|
||||||
|
|
||||||
|
class FileOp::FileAbstractImageImpl : public FileAbstractImage {
|
||||||
|
public:
|
||||||
|
FileAbstractImageImpl(FileOp* fop)
|
||||||
|
: m_doc(fop->document())
|
||||||
|
, m_sprite(m_doc->sprite())
|
||||||
|
, m_spec(m_sprite->spec())
|
||||||
|
, m_newBlend(fop->newBlend()) {
|
||||||
|
ASSERT(m_doc && m_sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSliceBounds(const gfx::Rect& sliceBounds) {
|
||||||
|
m_spec.setWidth(sliceBounds.w * m_scale.x);
|
||||||
|
m_spec.setHeight(sliceBounds.h * m_scale.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUnscaledImage(const doc::frame_t frame,
|
||||||
|
const doc::ImageRef& image) {
|
||||||
|
if (m_spec.width() == image->width() &&
|
||||||
|
m_spec.height() == image->height()) {
|
||||||
|
m_tmpScaledImage = image;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!m_tmpScaledImage)
|
||||||
|
m_tmpScaledImage.reset(doc::Image::create(m_spec));
|
||||||
|
|
||||||
|
doc::algorithm::resize_image(
|
||||||
|
image.get(),
|
||||||
|
m_tmpScaledImage.get(),
|
||||||
|
doc::algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||||
|
palette(frame),
|
||||||
|
m_sprite->rgbMap(frame),
|
||||||
|
image->maskColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileAbstractImage impl
|
||||||
|
doc::ImageSpec spec() const override {
|
||||||
|
return m_spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
os::ColorSpaceRef osColorSpace() const override {
|
||||||
|
return m_doc->osColorSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needAlpha() const override {
|
||||||
|
return m_sprite->needAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOpaque() const override {
|
||||||
|
return m_sprite->isOpaque();
|
||||||
|
}
|
||||||
|
|
||||||
|
int frames() const override {
|
||||||
|
return m_sprite->totalFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
int frameDuration(doc::frame_t frame) const override {
|
||||||
|
return m_sprite->frameDuration(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc::Palette* palette(doc::frame_t frame) const override {
|
||||||
|
ASSERT(m_sprite);
|
||||||
|
return m_sprite->palette(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc::PalettesList palettes() const override {
|
||||||
|
ASSERT(m_sprite);
|
||||||
|
return m_sprite->getPalettes();
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc::ImageRef getScaledImage() const override {
|
||||||
|
return m_tmpScaledImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* getScanline(int y) const override {
|
||||||
|
return m_tmpScaledImage->getPixelAddress(0, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderFrame(const doc::frame_t frame, doc::Image* dst) const override {
|
||||||
|
const bool needResize =
|
||||||
|
(dst->width() != m_sprite->width() ||
|
||||||
|
dst->height() != m_sprite->height());
|
||||||
|
|
||||||
|
if (needResize && !m_tmpUnscaledRender) {
|
||||||
|
auto spec = m_sprite->spec();
|
||||||
|
spec.setColorMode(dst->colorMode());
|
||||||
|
m_tmpUnscaledRender.reset(doc::Image::create(spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
render::Render render;
|
||||||
|
render.setNewBlend(m_newBlend);
|
||||||
|
render.setBgType(render::BgType::NONE);
|
||||||
|
|
||||||
|
render.renderSprite(
|
||||||
|
(needResize ? m_tmpUnscaledRender.get(): dst),
|
||||||
|
m_sprite, frame);
|
||||||
|
|
||||||
|
if (needResize) {
|
||||||
|
doc::algorithm::resize_image(
|
||||||
|
m_tmpUnscaledRender.get(),
|
||||||
|
dst,
|
||||||
|
doc::algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR,
|
||||||
|
palette(frame),
|
||||||
|
m_sprite->rgbMap(frame),
|
||||||
|
m_tmpUnscaledRender->maskColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setScale(const gfx::PointF& scale) {
|
||||||
|
m_scale = scale;
|
||||||
|
m_spec.setWidth(m_spec.width() * m_scale.x);
|
||||||
|
m_spec.setHeight(m_spec.height() * m_scale.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Doc* m_doc;
|
||||||
|
const doc::Sprite* m_sprite;
|
||||||
|
doc::ImageSpec m_spec;
|
||||||
|
bool m_newBlend;
|
||||||
|
doc::ImageRef m_tmpScaledImage = nullptr;
|
||||||
|
mutable doc::ImageRef m_tmpUnscaledRender = nullptr;
|
||||||
|
gfx::PointF m_scale = gfx::PointF(1.0, 1.0);
|
||||||
|
};
|
||||||
|
|
||||||
base::paths get_readable_extensions()
|
base::paths get_readable_extensions()
|
||||||
{
|
{
|
||||||
base::paths paths;
|
base::paths paths;
|
||||||
@ -586,6 +711,18 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
|
|||||||
return fop.release();
|
return fop.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool FileOp::checkIfFormatSupportResizeOnTheFly(const std::string& filename)
|
||||||
|
{
|
||||||
|
// Get the format through the extension of the filename
|
||||||
|
FileFormat* fileFormat =
|
||||||
|
FileFormatsManager::instance()->getFileFormat(
|
||||||
|
dio::detect_format_by_file_extension(filename));
|
||||||
|
|
||||||
|
return (fileFormat &&
|
||||||
|
fileFormat->support(FILE_ENCODE_ABSTRACT_IMAGE));
|
||||||
|
}
|
||||||
|
|
||||||
// Executes the file operation: loads or saves the sprite.
|
// Executes the file operation: loads or saves the sprite.
|
||||||
//
|
//
|
||||||
// It can be called from a different thread of the one used
|
// It can be called from a different thread of the one used
|
||||||
@ -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, ...)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2021 Igara Studio S.A.
|
// Copyright (C) 2018-2022 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -20,6 +20,7 @@
|
|||||||
#include "doc/image_ref.h"
|
#include "doc/image_ref.h"
|
||||||
#include "doc/pixel_format.h"
|
#include "doc/pixel_format.h"
|
||||||
#include "doc/selected_frames.h"
|
#include "doc/selected_frames.h"
|
||||||
|
#include "os/color_space.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -94,6 +95,33 @@ namespace app {
|
|||||||
doc::SelectedFrames m_selFrames;
|
doc::SelectedFrames m_selFrames;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Used by file formats with FILE_ENCODE_ABSTRACT_IMAGE flag, to
|
||||||
|
// encode a sprite with an intermediate transformation on-the-fly
|
||||||
|
// (e.g. resizing).
|
||||||
|
class FileAbstractImage {
|
||||||
|
public:
|
||||||
|
virtual ~FileAbstractImage() { }
|
||||||
|
virtual doc::ImageSpec spec() const = 0;
|
||||||
|
virtual os::ColorSpaceRef osColorSpace() const = 0;
|
||||||
|
virtual bool needAlpha() const = 0;
|
||||||
|
virtual bool isOpaque() const = 0;
|
||||||
|
virtual int frames() const = 0;
|
||||||
|
virtual int frameDuration(doc::frame_t frame) const = 0;
|
||||||
|
|
||||||
|
virtual const doc::Palette* palette(doc::frame_t frame) const = 0;
|
||||||
|
virtual doc::PalettesList palettes() const = 0;
|
||||||
|
|
||||||
|
virtual const doc::ImageRef getScaledImage() const = 0;
|
||||||
|
|
||||||
|
// In case the file format can encode scanline by scanline
|
||||||
|
// (e.g. PNG format).
|
||||||
|
virtual const uint8_t* getScanline(int y) const = 0;
|
||||||
|
|
||||||
|
// In case that the encoder needs full frame renders (or compare
|
||||||
|
// between frames), e.g. GIF format.
|
||||||
|
virtual void renderFrame(const doc::frame_t frame, doc::Image* dst) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
// Structure to load & save files.
|
// Structure to load & save files.
|
||||||
//
|
//
|
||||||
// TODO This class do to many things. There should be a previous
|
// TODO This class do to many things. There should be a previous
|
||||||
@ -113,6 +141,8 @@ namespace app {
|
|||||||
const std::string& filenameFormat,
|
const std::string& filenameFormat,
|
||||||
const bool ignoreEmptyFrames);
|
const bool ignoreEmptyFrames);
|
||||||
|
|
||||||
|
static bool checkIfFormatSupportResizeOnTheFly(const std::string& filename);
|
||||||
|
|
||||||
~FileOp();
|
~FileOp();
|
||||||
|
|
||||||
bool isSequence() const { return !m_seq.filename_list.empty(); }
|
bool isSequence() const { return !m_seq.filename_list.empty(); }
|
||||||
@ -189,8 +219,8 @@ namespace app {
|
|||||||
void sequenceGetColor(int index, int* r, int* g, int* b) const;
|
void sequenceGetColor(int index, int* r, int* g, int* b) const;
|
||||||
void sequenceSetAlpha(int index, int a);
|
void sequenceSetAlpha(int index, int a);
|
||||||
void sequenceGetAlpha(int index, int* a) const;
|
void sequenceGetAlpha(int index, int* a) const;
|
||||||
Image* sequenceImage(PixelFormat pixelFormat, int w, int h);
|
ImageRef sequenceImage(PixelFormat pixelFormat, int w, int h);
|
||||||
const Image* sequenceImage() const { return m_seq.image.get(); }
|
const ImageRef sequenceImage() const { return m_seq.image; }
|
||||||
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
const Palette* sequenceGetPalette() const { return m_seq.palette; }
|
||||||
bool sequenceGetHasAlpha() const {
|
bool sequenceGetHasAlpha() const {
|
||||||
return m_seq.has_alpha;
|
return m_seq.has_alpha;
|
||||||
@ -202,6 +232,11 @@ namespace app {
|
|||||||
return m_seq.flags;
|
return m_seq.flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Can be used to encode sequences/static files (e.g. png files)
|
||||||
|
// or animations (e.g. gif) resizing the result on the fly.
|
||||||
|
FileAbstractImage* abstractImage();
|
||||||
|
void setOnTheFlyScale(const gfx::PointF& scale);
|
||||||
|
|
||||||
const std::string& error() const { return m_error; }
|
const std::string& error() const { return m_error; }
|
||||||
void setError(const char *error, ...);
|
void setError(const char *error, ...);
|
||||||
bool hasError() const { return !m_error.empty(); }
|
bool hasError() const { return !m_error.empty(); }
|
||||||
@ -276,7 +311,11 @@ namespace app {
|
|||||||
int flags;
|
int flags;
|
||||||
} m_seq;
|
} m_seq;
|
||||||
|
|
||||||
|
class FileAbstractImageImpl;
|
||||||
|
std::unique_ptr<FileAbstractImageImpl> m_abstractImage;
|
||||||
|
|
||||||
void prepareForSequence();
|
void prepareForSequence();
|
||||||
|
void makeAbstractImage();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Available extensions for each load/save operation.
|
// Available extensions for each load/save operation.
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#define FILE_SUPPORT_TAGS 0x00001000
|
#define FILE_SUPPORT_TAGS 0x00001000
|
||||||
#define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors
|
#define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors
|
||||||
#define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000
|
#define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000
|
||||||
|
#define FILE_ENCODE_ABSTRACT_IMAGE 0x00008000 // Use the new FileAbstractImage
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-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;
|
||||||
|
@ -64,7 +64,8 @@ class JpegFormat : public FileFormat {
|
|||||||
FILE_SUPPORT_RGB |
|
FILE_SUPPORT_RGB |
|
||||||
FILE_SUPPORT_GRAY |
|
FILE_SUPPORT_GRAY |
|
||||||
FILE_SUPPORT_SEQUENCES |
|
FILE_SUPPORT_SEQUENCES |
|
||||||
FILE_SUPPORT_GET_FORMAT_OPTIONS;
|
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||||
|
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop) override;
|
bool onLoad(FileOp* fop) override;
|
||||||
@ -177,7 +178,7 @@ bool JpegFormat::onLoad(FileOp* fop)
|
|||||||
jpeg_start_decompress(&dinfo);
|
jpeg_start_decompress(&dinfo);
|
||||||
|
|
||||||
// Create the image.
|
// Create the image.
|
||||||
Image* image = fop->sequenceImage(
|
ImageRef image = fop->sequenceImage(
|
||||||
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
|
(dinfo.out_color_space == JCS_RGB ? IMAGE_RGB:
|
||||||
IMAGE_GRAYSCALE),
|
IMAGE_GRAYSCALE),
|
||||||
dinfo.output_width,
|
dinfo.output_width,
|
||||||
@ -352,7 +353,8 @@ bool JpegFormat::onSave(FileOp* fop)
|
|||||||
{
|
{
|
||||||
struct jpeg_compress_struct cinfo;
|
struct jpeg_compress_struct cinfo;
|
||||||
struct error_mgr jerr;
|
struct error_mgr jerr;
|
||||||
const Image* image = fop->sequenceImage();
|
const FileAbstractImage* img = fop->abstractImage();
|
||||||
|
const ImageSpec spec = img->spec();
|
||||||
JSAMPARRAY buffer;
|
JSAMPARRAY buffer;
|
||||||
JDIMENSION buffer_height;
|
JDIMENSION buffer_height;
|
||||||
const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
|
const auto jpeg_options = std::static_pointer_cast<JpegOptions>(fop->formatOptions());
|
||||||
@ -377,10 +379,10 @@ bool JpegFormat::onSave(FileOp* fop)
|
|||||||
jpeg_stdio_dest(&cinfo, file);
|
jpeg_stdio_dest(&cinfo, file);
|
||||||
|
|
||||||
// SET parameters for compression.
|
// SET parameters for compression.
|
||||||
cinfo.image_width = image->width();
|
cinfo.image_width = spec.width();
|
||||||
cinfo.image_height = image->height();
|
cinfo.image_height = spec.height();
|
||||||
|
|
||||||
if (image->pixelFormat() == IMAGE_GRAYSCALE) {
|
if (spec.colorMode() == ColorMode::GRAYSCALE) {
|
||||||
cinfo.input_components = 1;
|
cinfo.input_components = 1;
|
||||||
cinfo.in_color_space = JCS_GRAYSCALE;
|
cinfo.in_color_space = JCS_GRAYSCALE;
|
||||||
}
|
}
|
||||||
@ -427,15 +429,15 @@ bool JpegFormat::onSave(FileOp* fop)
|
|||||||
// Write each scan line.
|
// Write each scan line.
|
||||||
while (cinfo.next_scanline < cinfo.image_height) {
|
while (cinfo.next_scanline < cinfo.image_height) {
|
||||||
// RGB
|
// RGB
|
||||||
if (image->pixelFormat() == IMAGE_RGB) {
|
if (spec.colorMode() == ColorMode::RGB) {
|
||||||
uint32_t* src_address;
|
uint32_t* src_address;
|
||||||
uint8_t* dst_address;
|
uint8_t* dst_address;
|
||||||
int x, y;
|
int x, y;
|
||||||
for (y=0; y<(int)buffer_height; y++) {
|
for (y=0; y<(int)buffer_height; y++) {
|
||||||
src_address = (uint32_t*)image->getPixelAddress(0, cinfo.next_scanline+y);
|
src_address = (uint32_t*)img->getScanline(cinfo.next_scanline+y);
|
||||||
dst_address = ((uint8_t**)buffer)[y];
|
dst_address = ((uint8_t**)buffer)[y];
|
||||||
|
|
||||||
for (x=0; x<image->width(); ++x) {
|
for (x=0; x<spec.width(); ++x) {
|
||||||
c = *(src_address++);
|
c = *(src_address++);
|
||||||
*(dst_address++) = rgba_getr(c);
|
*(dst_address++) = rgba_getr(c);
|
||||||
*(dst_address++) = rgba_getg(c);
|
*(dst_address++) = rgba_getg(c);
|
||||||
@ -449,9 +451,9 @@ bool JpegFormat::onSave(FileOp* fop)
|
|||||||
uint8_t* dst_address;
|
uint8_t* dst_address;
|
||||||
int x, y;
|
int x, y;
|
||||||
for (y=0; y<(int)buffer_height; y++) {
|
for (y=0; y<(int)buffer_height; y++) {
|
||||||
src_address = (uint16_t*)image->getPixelAddress(0, cinfo.next_scanline+y);
|
src_address = (uint16_t*)img->getScanline(cinfo.next_scanline+y);
|
||||||
dst_address = ((uint8_t**)buffer)[y];
|
dst_address = ((uint8_t**)buffer)[y];
|
||||||
for (x=0; x<image->width(); ++x)
|
for (x=0; x<spec.width(); ++x)
|
||||||
*(dst_address++) = graya_getv(*(src_address++));
|
*(dst_address++) = graya_getv(*(src_address++));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2022 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -43,7 +44,8 @@ class PcxFormat : public FileFormat {
|
|||||||
FILE_SUPPORT_RGB |
|
FILE_SUPPORT_RGB |
|
||||||
FILE_SUPPORT_GRAY |
|
FILE_SUPPORT_GRAY |
|
||||||
FILE_SUPPORT_INDEXED |
|
FILE_SUPPORT_INDEXED |
|
||||||
FILE_SUPPORT_SEQUENCES;
|
FILE_SUPPORT_SEQUENCES |
|
||||||
|
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop) override;
|
bool onLoad(FileOp* fop) override;
|
||||||
@ -104,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 */
|
||||||
|
@ -54,7 +54,8 @@ class PngFormat : public FileFormat {
|
|||||||
FILE_SUPPORT_GRAYA |
|
FILE_SUPPORT_GRAYA |
|
||||||
FILE_SUPPORT_INDEXED |
|
FILE_SUPPORT_INDEXED |
|
||||||
FILE_SUPPORT_SEQUENCES |
|
FILE_SUPPORT_SEQUENCES |
|
||||||
FILE_SUPPORT_PALETTE_WITH_ALPHA;
|
FILE_SUPPORT_PALETTE_WITH_ALPHA |
|
||||||
|
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop) override;
|
bool onLoad(FileOp* fop) override;
|
||||||
@ -275,7 +276,7 @@ bool PngFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
int imageWidth = png_get_image_width(png, info);
|
int imageWidth = png_get_image_width(png, info);
|
||||||
int imageHeight = png_get_image_height(png, info);
|
int imageHeight = png_get_image_height(png, info);
|
||||||
Image* image = fop->sequenceImage(pixelFormat, imageWidth, imageHeight);
|
ImageRef image = fop->sequenceImage(pixelFormat, imageWidth, imageHeight);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
fop->setError("file_sequence_image %dx%d\n", imageWidth, imageHeight);
|
fop->setError("file_sequence_image %dx%d\n", imageWidth, imageHeight);
|
||||||
return false;
|
return false;
|
||||||
@ -550,23 +551,25 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
|
|
||||||
png_init_io(png, fp);
|
png_init_io(png, fp);
|
||||||
|
|
||||||
const Image* image = fop->sequenceImage();
|
const FileAbstractImage* img = fop->abstractImage();
|
||||||
switch (image->pixelFormat()) {
|
const ImageSpec spec = img->spec();
|
||||||
case IMAGE_RGB:
|
|
||||||
|
switch (spec.colorMode()) {
|
||||||
|
case ColorMode::RGB:
|
||||||
color_type =
|
color_type =
|
||||||
(fop->document()->sprite()->needAlpha() ||
|
(img->needAlpha() ||
|
||||||
fix_one_alpha_pixel ?
|
fix_one_alpha_pixel ?
|
||||||
PNG_COLOR_TYPE_RGB_ALPHA:
|
PNG_COLOR_TYPE_RGB_ALPHA:
|
||||||
PNG_COLOR_TYPE_RGB);
|
PNG_COLOR_TYPE_RGB);
|
||||||
break;
|
break;
|
||||||
case IMAGE_GRAYSCALE:
|
case ColorMode::GRAYSCALE:
|
||||||
color_type =
|
color_type =
|
||||||
(fop->document()->sprite()->needAlpha() ||
|
(img->needAlpha() ||
|
||||||
fix_one_alpha_pixel ?
|
fix_one_alpha_pixel ?
|
||||||
PNG_COLOR_TYPE_GRAY_ALPHA:
|
PNG_COLOR_TYPE_GRAY_ALPHA:
|
||||||
PNG_COLOR_TYPE_GRAY);
|
PNG_COLOR_TYPE_GRAY);
|
||||||
break;
|
break;
|
||||||
case IMAGE_INDEXED:
|
case ColorMode::INDEXED:
|
||||||
if (fix_one_alpha_pixel)
|
if (fix_one_alpha_pixel)
|
||||||
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||||
else
|
else
|
||||||
@ -574,8 +577,8 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const png_uint_32 width = image->width();
|
const png_uint_32 width = spec.width();
|
||||||
const png_uint_32 height = image->height();
|
const png_uint_32 height = spec.height();
|
||||||
|
|
||||||
png_set_IHDR(png, info, width, height, 8, color_type,
|
png_set_IHDR(png, info, width, height, 8, color_type,
|
||||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
|
||||||
@ -606,9 +609,9 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
png_set_unknown_chunks(png, info, &unknowns[0], num_unknowns);
|
png_set_unknown_chunks(png, info, &unknowns[0], num_unknowns);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fop->preserveColorProfile() &&
|
if (fop->preserveColorProfile() && spec.colorSpace()) {
|
||||||
fop->document()->sprite()->colorSpace())
|
saveColorSpace(png, info, spec.colorSpace().get());
|
||||||
saveColorSpace(png, info, fop->document()->sprite()->colorSpace().get());
|
}
|
||||||
|
|
||||||
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
||||||
int c, r, g, b;
|
int c, r, g, b;
|
||||||
@ -633,7 +636,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
// If the sprite does not have a (visible) background layer, we
|
// If the sprite does not have a (visible) background layer, we
|
||||||
// put alpha=0 to the transparent color.
|
// put alpha=0 to the transparent color.
|
||||||
int mask_entry = -1;
|
int mask_entry = -1;
|
||||||
if (fop->document()->sprite()->backgroundLayer() == NULL ||
|
if (fop->document()->sprite()->backgroundLayer() == nullptr ||
|
||||||
!fop->document()->sprite()->backgroundLayer()->isVisible()) {
|
!fop->document()->sprite()->backgroundLayer()->isVisible()) {
|
||||||
mask_entry = fop->document()->sprite()->transparentColor();
|
mask_entry = fop->document()->sprite()->transparentColor();
|
||||||
}
|
}
|
||||||
@ -668,8 +671,8 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
unsigned int x, c, a;
|
unsigned int x, c, a;
|
||||||
bool opaque = true;
|
bool opaque = true;
|
||||||
|
|
||||||
if (image->pixelFormat() == IMAGE_RGB) {
|
if (spec.colorMode() == ColorMode::RGB) {
|
||||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint32_t*)img->getScanline(y);
|
||||||
|
|
||||||
for (x=0; x<width; ++x) {
|
for (x=0; x<width; ++x) {
|
||||||
c = *(src_address++);
|
c = *(src_address++);
|
||||||
@ -690,8 +693,8 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
// In case that we are converting an indexed image to RGB just
|
// In case that we are converting an indexed image to RGB just
|
||||||
// to convert one pixel with alpha=254.
|
// to convert one pixel with alpha=254.
|
||||||
else if (image->pixelFormat() == IMAGE_INDEXED) {
|
else if (spec.colorMode() == ColorMode::INDEXED) {
|
||||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint8_t*)img->getScanline(y);
|
||||||
unsigned int x, c;
|
unsigned int x, c;
|
||||||
int r, g, b, a;
|
int r, g, b, a;
|
||||||
bool opaque = true;
|
bool opaque = true;
|
||||||
@ -716,7 +719,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_RGB) {
|
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_RGB) {
|
||||||
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint32_t*)img->getScanline(y);
|
||||||
unsigned int x, c;
|
unsigned int x, c;
|
||||||
|
|
||||||
for (x=0; x<width; ++x) {
|
for (x=0; x<width; ++x) {
|
||||||
@ -727,7 +730,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY_ALPHA) {
|
||||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint16_t*)img->getScanline(y);
|
||||||
unsigned int x, c, a;
|
unsigned int x, c, a;
|
||||||
bool opaque = true;
|
bool opaque = true;
|
||||||
|
|
||||||
@ -747,7 +750,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) {
|
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_GRAY) {
|
||||||
uint16_t* src_address = (uint16_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint16_t*)img->getScanline(y);
|
||||||
unsigned int x, c;
|
unsigned int x, c;
|
||||||
|
|
||||||
for (x=0; x<width; ++x) {
|
for (x=0; x<width; ++x) {
|
||||||
@ -756,7 +759,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE) {
|
else if (png_get_color_type(png, info) == PNG_COLOR_TYPE_PALETTE) {
|
||||||
uint8_t* src_address = (uint8_t*)image->getPixelAddress(0, y);
|
auto src_address = (const uint8_t*)img->getScanline(y);
|
||||||
unsigned int x;
|
unsigned int x;
|
||||||
|
|
||||||
for (x=0; x<width; ++x)
|
for (x=0; x<width; ++x)
|
||||||
@ -771,7 +774,7 @@ bool PngFormat::onSave(FileOp* fop)
|
|||||||
png_free(png, row_pointer);
|
png_free(png, row_pointer);
|
||||||
png_write_end(png, info);
|
png_write_end(png, info);
|
||||||
|
|
||||||
if (image->pixelFormat() == IMAGE_INDEXED) {
|
if (spec.colorMode() == ColorMode::INDEXED) {
|
||||||
png_free(png, palette);
|
png_free(png, palette);
|
||||||
palette = nullptr;
|
palette = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ bool SvgFormat::onLoad(FileOp* fop)
|
|||||||
|
|
||||||
bool SvgFormat::onSave(FileOp* fop)
|
bool SvgFormat::onSave(FileOp* fop)
|
||||||
{
|
{
|
||||||
const Image* image = fop->sequenceImage();
|
const ImageRef image = fop->sequenceImage();
|
||||||
int x, y, c, r, g, b, a, alpha;
|
int x, y, c, r, g, b, a, alpha;
|
||||||
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
const auto svg_options = std::static_pointer_cast<SvgOptions>(fop->formatOptions());
|
||||||
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
|
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
|
||||||
@ -103,7 +103,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
|||||||
case IMAGE_RGB: {
|
case IMAGE_RGB: {
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<RgbTraits>(image, x, y);
|
c = get_pixel_fast<RgbTraits>(image.get(), x, y);
|
||||||
alpha = rgba_geta(c);
|
alpha = rgba_geta(c);
|
||||||
if (alpha != 0x00)
|
if (alpha != 0x00)
|
||||||
printcol(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, pixelScaleValue);
|
printcol(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, pixelScaleValue);
|
||||||
@ -115,7 +115,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
|||||||
case IMAGE_GRAYSCALE: {
|
case IMAGE_GRAYSCALE: {
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<GrayscaleTraits>(image, x, y);
|
c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y);
|
||||||
auto v = graya_getv(c);
|
auto v = graya_getv(c);
|
||||||
alpha = graya_geta(c);
|
alpha = graya_geta(c);
|
||||||
if (alpha != 0x00)
|
if (alpha != 0x00)
|
||||||
@ -142,7 +142,7 @@ bool SvgFormat::onSave(FileOp* fop)
|
|||||||
}
|
}
|
||||||
for (y=0; y<image->height(); y++) {
|
for (y=0; y<image->height(); y++) {
|
||||||
for (x=0; x<image->width(); x++) {
|
for (x=0; x<image->width(); x++) {
|
||||||
c = get_pixel_fast<IndexedTraits>(image, x, y);
|
c = get_pixel_fast<IndexedTraits>(image.get(), x, y);
|
||||||
if (c != mask_color)
|
if (c != mask_color)
|
||||||
printcol(x, y, image_palette[c][0] & 0xff,
|
printcol(x, y, image_palette[c][0] & 0xff,
|
||||||
image_palette[c][1] & 0xff,
|
image_palette[c][1] & 0xff,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
@ -53,7 +53,8 @@ class TgaFormat : public FileFormat {
|
|||||||
FILE_SUPPORT_INDEXED |
|
FILE_SUPPORT_INDEXED |
|
||||||
FILE_SUPPORT_SEQUENCES |
|
FILE_SUPPORT_SEQUENCES |
|
||||||
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
FILE_SUPPORT_GET_FORMAT_OPTIONS |
|
||||||
FILE_SUPPORT_PALETTE_WITH_ALPHA;
|
FILE_SUPPORT_PALETTE_WITH_ALPHA |
|
||||||
|
FILE_ENCODE_ABSTRACT_IMAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onLoad(FileOp* fop) override;
|
bool onLoad(FileOp* fop) override;
|
||||||
@ -164,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();
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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 {
|
||||||
|
@ -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") {
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user