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