Merge branch 'master' into tilemap-editor

This commit is contained in:
David Capello 2020-06-12 11:33:55 -03:00
commit bd1723313a
65 changed files with 801 additions and 205 deletions

View File

@ -0,0 +1,19 @@
GIMP Palette
Channels: RGBA
#
0 0 0 0 Transparent
1 1 1 255 Black
62 184 73 255 Medium green
116 208 125 255 Light green
89 85 224 255 Dark blue
128 118 241 255 Light blue
185 94 81 255 Dark red
101 219 239 255 Cyan
219 101 89 255 Medium red
255 137 125 255 Light red
204 195 94 255 Dark yellow
222 208 135 255 Light yellow
58 162 65 255 Dark green
183 102 181 255 Magenta
204 204 204 255 Gray
255 255 255 255 White

View File

@ -0,0 +1,19 @@
GIMP Palette
Channels: RGBA
#
0 0 0 0 Transparent
1 1 1 255 Black
36 219 36 255 Medium green
109 255 109 255 Light green
36 36 255 255 Dark blue
73 109 255 255 Light blue
182 36 36 255 Dark red
73 219 255 255 Cyan
255 36 36 255 Medium red
255 109 109 255 Light red
219 219 36 255 Dark yellow
219 219 146 255 Light yellow
36 146 36 255 Dark green
219 73 182 255 Magenta
182 182 182 255 Gray
255 255 255 255 White

View File

@ -26,6 +26,8 @@
{ "id": "Game Boy", "path": "./gameboy.gpl" },
{ "id": "Game Boy Color Type1", "path": "./gameboy-color-type1.gpl" },
{ "id": "Master System", "path": "./master-system.gpl" },
{ "id": "MSX1", "path": "./msx1.gpl" },
{ "id": "MSX2", "path": "./msx2.gpl" },
{ "id": "NES", "path": "./nes.gpl" },
{ "id": "NES NTSC", "path": "./nes-ntsc.gpl" },
{ "id": "Teletext", "path": "./teletext.gpl" },

View File

@ -0,0 +1,54 @@
GIMP Palette
Name: Minecraft
#
# Based on Minecraft 1.14 default texture
# Order: wool, concrete, terracotta
#
234 237 237 white_wool
241 119 22 orange_wool
190 70 181 magenta_wool
60 176 218 light_blue_wool
249 198 41 yellow_wool
113 186 26 lime_wool
238 144 173 pink_wool
63 69 72 gray_wool
142 143 135 light_gray_wool
21 138 145 cyan_wool
123 43 173 purple_wool
53 58 158 blue_wool
115 72 41 brown_wool
85 110 28 green_wool
161 40 35 red_wool
22 22 27 black_wool
207 213 214 white_concrete
224 97 1 orange_concrete
169 48 159 magenta_concrete
36 137 199 light_blue_concrete
241 175 21 yellow_concrete
94 169 25 lime_concrete
214 101 143 pink_concrete
55 58 62 gray_concrete
125 125 115 light_gray_concrete
21 119 136 cyan_concrete
100 32 156 purple_concrete
45 47 143 blue_concrete
96 60 32 brown_concrete
73 91 36 green_concrete
142 33 33 red_concrete
8 10 15 black_concrete
210 178 161 white_terracotta
162 84 38 orange_terracotta
150 88 109 magenta_terracotta
114 109 138 light_blue_terracotta
186 133 35 yellow_terracotta
104 118 53 lime_terracotta
162 78 79 pink_terracotta
58 42 36 gray_terracotta
135 107 98 light_gray_terracotta
87 91 91 cyan_terracotta
118 70 86 purple_terracotta
74 60 91 blue_terracotta
77 51 36 brown_terracotta
76 83 42 green_terracotta
143 61 47 red_terracotta
37 23 16 black_terracotta

View File

@ -10,6 +10,7 @@
"contributes": {
"palettes": [
{ "id": "Google UI", "path": "./google-ui.gpl" },
{ "id": "Minecraft", "path": "./minecraft.gpl" },
{ "id": "Monokai", "path": "./monokai.gpl" },
{ "id": "SmileBASIC", "path": "./smile-basic.gpl" },
{ "id": "Solarized", "path": "./solarized.gpl" },

View File

@ -21,7 +21,6 @@
<key command="ExportSpriteSheet" shortcut="Ctrl+E" mac="Cmd+E" />
<key command="RepeatLastExport" shortcut="Ctrl+Shift+X" mac="Cmd+Shift+X" />
<key command="AdvancedMode" shortcut="Ctrl+F" />
<key command="DeveloperConsole" shortcut="F12" />
<key command="Exit" win="Ctrl+Q" linux="Ctrl+Q" mac="Cmd+Q" />
<key command="Exit" win="Alt+F4" />
<key command="Cancel" shortcut="Esc">
@ -527,6 +526,9 @@
<param name="save" value="true" />
<param name="srgb" value="false" />
</key>
<key command="Screenshot" shortcut="F12">
<param name="steam" value="true" />
</key>
</commands>
<!-- Keyboard shortcuts to select tools -->

View File

@ -143,6 +143,7 @@
<option id="timeline_layer_panel_width" type="int" default="100" />
<option id="show_menu_bar" type="bool" default="true" />
<option id="recent_items" type="int" default="16" />
<option id="osx_async_view" type="bool" default="true" />
</section>
<section id="undo" text="Undo">
<option id="size_limit" type="int" default="0" />

View File

@ -13,6 +13,7 @@ sprite_without_profile = The sprite doesn't contain a color profile.
[statusbar_tips]
all_layers_are_locked = All selected layers are locked
layer_locked = Layer '{0}' is locked
[alerts]
applying_filter = FX<<Applying effect...||&Cancel
@ -433,6 +434,7 @@ SavePalette = Save Palette
Screenshot = Screenshot
Screenshot_Open = Take & Open Screenshot
Screenshot_Save = Take & Save Screenshot
Screenshot_Steam = Take & Add Screenshot to Steam
Screenshot_sRGB = (sRGB Color Profile)
Screenshot_DisplayCS = (Display Color Profile)
Scroll = Scroll {0}
@ -473,6 +475,7 @@ SpriteProperties = Sprite Properties
SpriteSize = Sprite Size
Stroke = Stroke Selection Borders with Foreground Color
SwitchColors = Switch Colors
SwapCheckerboardColors = Swap Checkerboard Background Colors
SwitchNonactiveLayersOpacity = Switch Nonactive Layers Opacity
SymmetryMode = Symmetry Mode
TiledMode = Tiled Mode

2
laf

@ -1 +1 @@
Subproject commit af0f8e7b53b9e3e689b5fa4e5ce1466c42c9e2aa
Subproject commit 9c6ecadd9cbcfd2b87b7cfa61e01466013da3797

View File

@ -32,7 +32,7 @@ because they don't depend on any other component.
## Level 2
* [doc](doc/) (base, fixmath, gfx, os): Document model library.
* [doc](doc/) (base, fixmath, gfx): Document model library.
* [ui](ui/) (base, gfx, os): Portable UI library (buttons, windows, text fields, etc.)
* [updater](updater/) (base, cfg, net): Component to check for updates.

View File

@ -286,6 +286,7 @@ if(ENABLE_UI)
commands/cmd_show.cpp
commands/cmd_slice_properties.cpp
commands/cmd_sprite_properties.cpp
commands/cmd_swap_checkerboard_colors.cpp
commands/cmd_switch_colors.cpp
commands/cmd_symmetry_mode.cpp
commands/cmd_tiled_mode.cpp
@ -611,6 +612,7 @@ add_library(app-lib
util/autocrop.cpp
util/buffer_region.cpp
util/cel_ops.cpp
util/conversion_to_surface.cpp
util/expand_cel_canvas.cpp
util/filetoks.cpp
util/freetype_utils.cpp

View File

@ -69,6 +69,10 @@
#include "ui/ui.h"
#include "ver/info.h"
#ifdef __APPLE__
#include "os/osx/system.h"
#endif
#include <iostream>
#include <memory>
@ -239,6 +243,11 @@ int App::initialize(const AppOptions& options)
system->setTabletAPI(os::TabletAPI::Wintab);
#endif
#ifdef __APPLE__
if (!preferences().general.osxAsyncView())
os::osx_set_async_view(false);
#endif
system->setAppName(get_app_name());
system->setAppMode(m_isGui ? os::AppMode::GUI:
os::AppMode::CLI);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -134,7 +134,11 @@ void CopyCel::onFireNotifications()
{
CmdSequence::onFireNotifications();
ASSERT(m_srcLayer.layer());
// The m_srcLayer can be nullptr now because the layer from where we
// copied this cel might not exist anymore (e.g. if we copied the
// cel from another document that is already closed)
//ASSERT(m_srcLayer.layer());
ASSERT(m_dstLayer.layer());
static_cast<Doc*>(m_dstLayer.layer()->sprite()->document())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -9,12 +9,11 @@
#include "config.h"
#endif
#include "app/cmd/background_from_layer.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/doc_api.h"
#include "app/modules/gui.h"
#include "app/tx.h"
#include "app/ui/color_bar.h"
#include "doc/layer.h"
#include "doc/sprite.h"
@ -56,9 +55,8 @@ void BackgroundFromLayerCommand::onExecute(Context* context)
Doc* document(writer.document());
{
Tx tx(writer.context(), "Background from Layer");
document->getApi(tx).backgroundFromLayer(
static_cast<LayerImage*>(writer.layer()));
Tx tx(writer.context(), friendlyName());
tx(new cmd::BackgroundFromLayer(static_cast<LayerImage*>(writer.layer())));
tx.commit();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -21,7 +21,7 @@
#include "app/ui/editor/editor_render.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/status_bar.h"
#include "doc/conversion_to_surface.h"
#include "app/util/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/primitives.h"
@ -231,7 +231,7 @@ protected:
break;
}
doc::convert_image_to_surface(m_doublebuf.get(), m_pal,
convert_image_to_surface(m_doublebuf.get(), m_pal,
m_doublesur, 0, 0, 0, 0, m_doublebuf->width(), m_doublebuf->height());
g->blit(m_doublesur, 0, 0, 0, 0, m_doublesur->width(), m_doublesur->height());
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -8,14 +9,13 @@
#include "config.h"
#endif
#include "app/cmd/layer_from_background.h"
#include "app/commands/command.h"
#include "app/context_access.h"
#include "app/doc_api.h"
#include "app/modules/gui.h"
#include "app/tx.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "ui/ui.h"
namespace app {
@ -51,8 +51,8 @@ void LayerFromBackgroundCommand::onExecute(Context* context)
ContextWriter writer(context);
Doc* document(writer.document());
{
Tx tx(writer.context(), "Layer from Background");
document->getApi(tx).layerFromBackground(writer.layer());
Tx tx(writer.context(), friendlyName());
tx(new cmd::LayerFromBackground(writer.layer()));
tx.commit();
}
#ifdef ENABLE_UI

View File

@ -0,0 +1,53 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app.h"
#include "app/commands/command.h"
#include "app/modules/editors.h"
#include "app/ui/editor/editor.h"
#include "ui/base.h"
#include "app/context.h"
namespace app {
class SwapCheckerboardColorsCommand : public Command {
public:
SwapCheckerboardColorsCommand();
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
SwapCheckerboardColorsCommand::SwapCheckerboardColorsCommand()
: Command(CommandId::SwapCheckerboardColors(), CmdUIOnlyFlag)
{
}
bool SwapCheckerboardColorsCommand::onEnabled(Context* context)
{
return true;
}
void SwapCheckerboardColorsCommand::onExecute(Context* context)
{
DocumentPreferences& docPref = Preferences::instance().document(context->activeDocument());
app::Color c1 = docPref.bg.color1();
app::Color c2 = docPref.bg.color2();
docPref.bg.color1(c2);
docPref.bg.color2(c1);
}
Command* CommandFactory::createSwapCheckerboardColorsCommand()
{
return new SwapCheckerboardColorsCommand;
}
} // namespace app

View File

@ -153,6 +153,7 @@ FOR_EACH_COMMAND(SliceProperties)
FOR_EACH_COMMAND(SnapToGrid)
FOR_EACH_COMMAND(SpriteProperties)
FOR_EACH_COMMAND(Stroke)
FOR_EACH_COMMAND(SwapCheckerboardColors)
FOR_EACH_COMMAND(SwitchColors)
FOR_EACH_COMMAND(SymmetryMode)
FOR_EACH_COMMAND(TiledMode)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -17,15 +17,23 @@
#include "app/file/file.h"
#include "app/i18n/strings.h"
#include "app/resource_finder.h"
#include "base/buffer.h"
#include "base/fs.h"
#include "doc/cel.h"
#include "doc/color.h"
#include "doc/image_impl.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "os/display.h"
#include "os/surface.h"
#include "ui/alert.h"
#include "ui/manager.h"
#include "ui/scale.h"
#ifdef ENABLE_STEAM
#include "steam/steam.h"
#endif
namespace app {
@ -34,6 +42,9 @@ using namespace ui;
struct ScreenshotParams : public NewParams {
Param<bool> save { this, false, "save" };
Param<bool> srgb { this, true, "srgb" };
#ifdef ENABLE_STEAM
Param<bool> steam { this, false, "steam" };
#endif
};
class ScreenshotCommand : public CommandWithNewParams<ScreenshotParams> {
@ -85,9 +96,11 @@ void ScreenshotCommand::onExecute(Context* ctx)
doc::Cel* cel = spr->firstLayer()->cel(0);
doc::Image* img = cel->image();
const int w = img->width();
const int h = img->height();
for (int y=0; y<img->height(); ++y) {
for (int x=0; x<img->width(); ++x) {
for (int y=0; y<h; ++y) {
for (int x=0; x<w; ++x) {
gfx::Color c = surface->getPixel(x, y);
img->putPixel(x, y, doc::rgba(gfx::getr(c),
@ -103,6 +116,34 @@ void ScreenshotCommand::onExecute(Context* ctx)
if (params().srgb())
cmd::convert_color_profile(spr, gfx::ColorSpace::MakeSRGB());
#ifdef ENABLE_STEAM
if (params().steam()) {
if (auto steamAPI = steam::SteamAPI::instance()) {
// Get image again (cmd::convert_color_profile() might have changed it)
img = cel->image();
const int scale = display->scale();
base::buffer rgbBuffer(3*w*h*scale*scale);
int c = 0;
doc::LockImageBits<RgbTraits> bits(img);
for (int y=0; y<h; ++y) {
for (int i=0; i<scale; ++i) {
for (int x=0; x<w; ++x) {
color_t color = get_pixel_fast<RgbTraits>(img, x, y);
for (int j=0; j<scale; ++j) {
rgbBuffer[c++] = doc::rgba_getr(color);
rgbBuffer[c++] = doc::rgba_getg(color);
rgbBuffer[c++] = doc::rgba_getb(color);
}
}
}
}
if (steamAPI->writeScreenshot(&rgbBuffer[0], rgbBuffer.size(), w*scale, h*scale))
return;
}
}
#endif
if (params().save()) {
save_document(ctx, doc.get());
}
@ -115,6 +156,11 @@ void ScreenshotCommand::onExecute(Context* ctx)
std::string ScreenshotCommand::onGetFriendlyName() const
{
std::string name;
#ifdef ENABLE_STEAM
if (params().steam())
name = Strings::commands_Screenshot_Steam();
else
#endif
if (params().save())
name = Strings::commands_Screenshot_Save();
else

View File

@ -37,7 +37,7 @@ void ContextFlags::update(Context* context)
if (document) {
m_flags |= HasActiveDocument;
if (document->lock(Doc::ReadLock, 0)) {
if (document->readLock(0)) {
m_flags |= ActiveDocumentIsReadable;
if (document->isMaskVisible())

View File

@ -367,14 +367,18 @@ private:
}
// Read color space
gfx::ColorSpacePtr colorSpace = readColorSpace(s);
if (colorSpace)
spr->setColorSpace(colorSpace);
if (!s.eof()) {
gfx::ColorSpacePtr colorSpace = readColorSpace(s);
if (colorSpace)
spr->setColorSpace(colorSpace);
}
// Read grid bounds
gfx::Rect gridBounds = readGridBounds(s);
if (!gridBounds.isEmpty())
spr->setGridBounds(gridBounds);
if (!s.eof()) {
gfx::Rect gridBounds = readGridBounds(s);
if (!gridBounds.isEmpty())
spr->setGridBounds(gridBounds);
}
return spr.release();
}
@ -384,6 +388,12 @@ private:
const gfx::ColorSpace::Flag flags = (gfx::ColorSpace::Flag)read16(s);
const double gamma = fixmath::fixtof(read32(s));
const size_t n = read32(s);
// If the color space file is to big, it's because the sprite file
// is invalid or or from an old session without color spcae.
if (n > 1024*1024*64) // 64 MB is too much for an ICC file
return nullptr;
std::vector<uint8_t> buf(n);
if (n)
s.read((char*)&buf[0], n);

View File

@ -95,6 +95,46 @@ void Doc::setContext(Context* ctx)
onContextChanged();
}
bool Doc::canWriteLockFromRead() const
{
return m_rwLock.canWriteLockFromRead();
}
bool Doc::readLock(int timeout)
{
return m_rwLock.lock(base::RWLock::ReadLock, timeout);
}
bool Doc::writeLock(int timeout)
{
return m_rwLock.lock(base::RWLock::WriteLock, timeout);
}
bool Doc::upgradeToWrite(int timeout)
{
return m_rwLock.upgradeToWrite(timeout);
}
void Doc::downgradeToRead()
{
m_rwLock.downgradeToRead();
}
void Doc::unlock()
{
m_rwLock.unlock();
}
bool Doc::weakLock(base::RWLock::WeakLock* weak_lock_flag)
{
return m_rwLock.weakLock(weak_lock_flag);
}
void Doc::weakUnlock()
{
m_rwLock.weakUnlock();
}
void Doc::setTransaction(Transaction* transaction)
{
if (transaction) {
@ -188,7 +228,7 @@ void Doc::notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer)
void Doc::notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame)
{
DocEvent ev(this);
ev.sprite(fromLayer->sprite());
ev.sprite(toLayer->sprite());
ev.layer(fromLayer);
ev.frame(fromFrame);
ev.targetLayer(toLayer);
@ -199,8 +239,8 @@ void Doc::notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, fr
void Doc::notifyCelCopied(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame)
{
DocEvent ev(this);
ev.sprite(fromLayer->sprite());
ev.layer(fromLayer);
ev.sprite(toLayer->sprite());
ev.layer(fromLayer); // From layer can be nullptr
ev.frame(fromFrame);
ev.targetLayer(toLayer);
ev.targetFrame(toFrame);

View File

@ -58,7 +58,6 @@ namespace app {
// An application document. It is the class used to contain one file
// opened and being edited by the user (a sprite).
class Doc : public doc::Document,
public base::RWLock,
public obs::observable<DocObserver> {
enum Flags {
kAssociatedToFile = 1, // This sprite is associated to a file in the file-system
@ -73,6 +72,17 @@ namespace app {
Context* context() const { return m_ctx; }
void setContext(Context* ctx);
// Lock/unlock API (RWLock wrapper)
bool canWriteLockFromRead() const;
bool readLock(int timeout);
bool writeLock(int timeout);
bool upgradeToWrite(int timeout);
void downgradeToRead();
void unlock();
bool weakLock(base::RWLock::WeakLock* weak_lock_flag);
void weakUnlock();
// Sets active/running transaction.
void setTransaction(Transaction* transaction);
Transaction* transaction() { return m_transaction; }
@ -216,9 +226,15 @@ namespace app {
void removeFromContext();
void updateOSColorSpace(bool appWideSignal);
// The document is in the collection of documents of this context.
Context* m_ctx;
// Internal states of the document.
int m_flags;
// Read-Write locks.
base::RWLock m_rwLock;
// Undo and redo information about the document.
std::unique_ptr<DocUndo> m_undo;

View File

@ -82,13 +82,13 @@ namespace app {
explicit DocReader(Doc* doc, int timeout)
: DocAccess(doc) {
if (m_doc && !m_doc->lock(Doc::ReadLock, timeout))
if (m_doc && !m_doc->readLock(timeout))
throw CannotReadDocException();
}
explicit DocReader(const DocReader& copy, int timeout)
: DocAccess(copy) {
if (m_doc && !m_doc->lock(Doc::ReadLock, timeout))
if (m_doc && !m_doc->readLock(timeout))
throw CannotReadDocException();
}
@ -126,7 +126,7 @@ namespace app {
, m_from_reader(false)
, m_locked(false) {
if (m_doc) {
if (!m_doc->lock(Doc::WriteLock, timeout))
if (!m_doc->writeLock(timeout))
throw CannotWriteDocException();
m_locked = true;

View File

@ -14,13 +14,11 @@
#include "app/cmd/add_cel.h"
#include "app/cmd/add_frame.h"
#include "app/cmd/add_layer.h"
#include "app/cmd/background_from_layer.h"
#include "app/cmd/clear_cel.h"
#include "app/cmd/clear_image.h"
#include "app/cmd/copy_cel.h"
#include "app/cmd/copy_frame.h"
#include "app/cmd/flip_image.h"
#include "app/cmd/layer_from_background.h"
#include "app/cmd/move_cel.h"
#include "app/cmd/move_layer.h"
#include "app/cmd/remove_cel.h"
@ -678,16 +676,6 @@ void DocApi::restackLayerBefore(Layer* layer, LayerGroup* parent, Layer* beforeT
restackLayerAfter(layer, parent, afterThis);
}
void DocApi::backgroundFromLayer(Layer* layer)
{
m_transaction.execute(new cmd::BackgroundFromLayer(layer));
}
void DocApi::layerFromBackground(Layer* layer)
{
m_transaction.execute(new cmd::LayerFromBackground(layer));
}
Layer* DocApi::duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer* afterLayer)
{
ASSERT(parent);

View File

@ -97,8 +97,6 @@ namespace app {
void removeLayer(Layer* layer);
void restackLayerAfter(Layer* layer, LayerGroup* parent, Layer* afterThis);
void restackLayerBefore(Layer* layer, LayerGroup* parent, Layer* beforeThis);
void backgroundFromLayer(Layer* layer);
void layerFromBackground(Layer* layer);
Layer* duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer* afterLayer);
Layer* duplicateLayerBefore(Layer* sourceLayer, LayerGroup* parent, Layer* beforeLayer);

View File

@ -47,6 +47,7 @@
#include "render/render.h"
#include "ver/info.h"
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iomanip>
@ -1361,68 +1362,86 @@ void DocExporter::createDataFile(const Samples& samples,
// meta.layers
if (m_listLayers) {
os << ",\n"
<< " \"layers\": [";
bool firstLayer = true;
LayerList metaLayers;
for (auto& item : m_documents) {
Doc* doc = item.doc;
Sprite* sprite = doc->sprite();
LayerList layers;
Layer* root = sprite->root();
LayerList layers;
if (item.selLayers)
layers = item.selLayers->toLayerList();
else
layers = sprite->allVisibleLayers();
for (Layer* layer : layers) {
if (firstLayer)
firstLayer = false;
else
os << ",";
os << "\n { \"name\": \"" << escape_for_json(layer->name()) << "\"";
if (layer->parent() != layer->sprite()->root())
os << ", \"group\": \"" << escape_for_json(layer->parent()->name()) << "\"";
if (LayerImage* layerImg = dynamic_cast<LayerImage*>(layer)) {
os << ", \"opacity\": " << layerImg->opacity()
<< ", \"blendMode\": \"" << blend_mode_to_string(layerImg->blendMode()) << "\"";
// If this layer is inside a group, check that the group will
// be included in the meta data too.
Layer* group = layer->parent();
int pos = int(metaLayers.size());
while (group && group != root) {
if (std::find(metaLayers.begin(), metaLayers.end(), group) == metaLayers.end()) {
metaLayers.insert(metaLayers.begin()+pos, group);
}
group = group->parent();
}
os << layer->userData();
// Insert the layer
if (std::find(metaLayers.begin(), metaLayers.end(), layer) == metaLayers.end()) {
metaLayers.push_back(layer);
}
}
}
// Cels
CelList cels;
layer->getCels(cels);
bool someCelWithData = false;
bool firstLayer = true;
os << ",\n"
<< " \"layers\": [";
for (Layer* layer : metaLayers) {
if (firstLayer)
firstLayer = false;
else
os << ",";
os << "\n { \"name\": \"" << escape_for_json(layer->name()) << "\"";
if (layer->parent() != layer->sprite()->root())
os << ", \"group\": \"" << escape_for_json(layer->parent()->name()) << "\"";
if (LayerImage* layerImg = dynamic_cast<LayerImage*>(layer)) {
os << ", \"opacity\": " << layerImg->opacity()
<< ", \"blendMode\": \"" << blend_mode_to_string(layerImg->blendMode()) << "\"";
}
os << layer->userData();
// Cels
CelList cels;
layer->getCels(cels);
bool someCelWithData = false;
for (const Cel* cel : cels) {
if (!cel->data()->userData().isEmpty()) {
someCelWithData = true;
break;
}
}
if (someCelWithData) {
bool firstCel = true;
os << ", \"cels\": [";
for (const Cel* cel : cels) {
if (!cel->data()->userData().isEmpty()) {
someCelWithData = true;
break;
if (firstCel)
firstCel = false;
else
os << ", ";
os << "{ \"frame\": " << cel->frame()
<< cel->data()->userData()
<< " }";
}
}
if (someCelWithData) {
bool firstCel = true;
os << ", \"cels\": [";
for (const Cel* cel : cels) {
if (!cel->data()->userData().isEmpty()) {
if (firstCel)
firstCel = false;
else
os << ", ";
os << "{ \"frame\": " << cel->frame()
<< cel->data()->userData()
<< " }";
}
}
os << "]";
}
os << " }";
os << "]";
}
os << " }";
}
os << "\n ]";
}

View File

@ -50,6 +50,12 @@ void DocRange::clearRange()
m_flags = kNone;
m_selectedLayers.clear();
m_selectedFrames.clear();
// Reset the starting point of a previous startRange/endRange(), we
// don't want to store a pointer to an invalid
// "m_selectingFromLayer" layer.
m_selectingFromLayer = nullptr;
m_selectingFromFrame = -1;
}
void DocRange::startRange(Layer* fromLayer, frame_t fromFrame, Type type)
@ -100,6 +106,14 @@ void DocRange::eraseAndAdjust(const Layer* layer)
if (!enabled())
return;
// Check that the sprite of m_selectingFromLayer is the same than
// the given layer. In the past if we stored an invalid
// "m_selectingFromLayer" for too much time this could fail (even
// more, "m_selectingFromLayer" could be pointing to an already
// closed/deleted sprite).
ASSERT(!m_selectingFromLayer || !layer ||
m_selectingFromLayer->sprite() == layer->sprite());
if (m_selectingFromLayer)
m_selectingFromLayer = candidate_if_layer_is_deleted(m_selectingFromLayer, layer);

View File

@ -93,7 +93,7 @@ bool SvgFormat::onSave(FileOp* fop)
fprintf(f, "/>\n");
};
fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
fprintf(f, "<svg version=\"1.1\" width=\"%d\" height=\"%d\" xmlns=\"http://www.w3.org/2000/svg\">\n",
fprintf(f, "<svg version=\"1.1\" width=\"%d\" height=\"%d\" xmlns=\"http://www.w3.org/2000/svg\" shape-rendering=\"crispEdges\">\n",
image->width()*pixelScaleValue, image->height()*pixelScaleValue);
switch (image->pixelFormat()) {

View File

@ -48,6 +48,10 @@
#include "ui/intern.h"
#include "ui/ui.h"
#ifdef ENABLE_STEAM
#include "steam/steam.h"
#endif
#include <algorithm>
#include <list>
#include <vector>
@ -366,6 +370,11 @@ void defer_invalid_rect(const gfx::Rect& rc)
// Manager event handler.
bool CustomizedGuiManager::onProcessMessage(Message* msg)
{
#ifdef ENABLE_STEAM
if (auto steamAPI = steam::SteamAPI::instance())
steamAPI->runCallbacks();
#endif
switch (msg->type()) {
case kCloseDisplayMessage: {

View File

@ -16,12 +16,12 @@
#include "app/doc.h"
#include "app/file/file.h"
#include "app/file_system.h"
#include "app/util/conversion_to_surface.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/scoped_lock.h"
#include "base/thread.h"
#include "doc/algorithm/rotate.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/primitives.h"

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2018 David Capello
// Copyright (C) 2016 Carlo Caputo
//
@ -10,9 +10,9 @@
#include "config.h"
#endif
#include "app/util/conversion_to_surface.h"
#include "doc/blend_mode.h"
#include "doc/cel.h"
#include "doc/conversion_to_surface.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "os/surface.h"

View File

@ -460,6 +460,7 @@ public:
void setFinalStep(ToolLoop* loop, bool state) override {
m_modify_selection = state;
int modifiers = int(loop->getModifiers());
if (state) {
m_maxBounds = loop->getMask()->bounds();
@ -467,11 +468,16 @@ public:
m_mask.copyFrom(loop->getMask());
m_mask.freeze();
m_mask.reserve(loop->sprite()->bounds());
if ((modifiers & int(ToolLoopModifiers::kIntersectSelection)) != 0) {
m_intersectMask.clear();
m_intersectMask.reserve(loop->sprite()->bounds());
}
}
else {
int modifiers = int(loop->getModifiers());
if ((modifiers & int(ToolLoopModifiers::kIntersectSelection)) != 0) {
m_mask.intersect(m_intersectMask);
m_intersectMask.clear();
}
// We can intersect the used bounds in inkHline() calls to

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -26,10 +26,10 @@
#include "app/ui/main_window.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
#include "app/util/conversion_to_surface.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "doc/brush.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "gfx/border.h"

View File

@ -51,7 +51,6 @@
#include "base/fs.h"
#include "base/scoped_value.h"
#include "doc/brush.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/remap.h"
@ -990,7 +989,11 @@ public:
if (!m_popup) {
m_popup.reset(new DynamicsPopup(this));
m_popup->Close.connect([this](CloseEvent&){ deselectItems(); });
m_popup->Close.connect(
[this](CloseEvent&){
deselectItems();
m_dynamics = m_popup->getDynamics();
});
}
const gfx::Rect bounds = this->bounds();
@ -1000,11 +1003,10 @@ public:
m_popup->openWindow();
}
tools::DynamicsOptions getDynamics() {
if (m_popup)
return m_popup->getDynamics();
else
return tools::DynamicsOptions();
const tools::DynamicsOptions& getDynamics() const {
if (m_popup && m_popup->isVisible())
m_dynamics = m_popup->getDynamics();
return m_dynamics;
}
private:
@ -1023,11 +1025,13 @@ private:
Preferences::instance().tool(tool).brush.angle(angle);
}
// ButtonSet overrides
void onItemChange(Item* item) override {
ButtonSet::onItemChange(item);
switchPopup();
}
// Widget overrides
void onInitTheme(InitThemeEvent& ev) override {
ButtonSet::onInitTheme(ev);
if (m_popup)
@ -1036,6 +1040,7 @@ private:
std::unique_ptr<DynamicsPopup> m_popup;
ContextBar* m_ctxBar;
mutable tools::DynamicsOptions m_dynamics;
};
class ContextBar::FreehandAlgorithmField : public CheckBox {
@ -2220,7 +2225,7 @@ render::GradientType ContextBar::gradientType()
return m_gradientType->gradientType();
}
tools::DynamicsOptions ContextBar::getDynamics()
const tools::DynamicsOptions& ContextBar::getDynamics() const
{
return m_dynamics->getDynamics();
}

View File

@ -92,7 +92,7 @@ namespace app {
render::GradientType gradientType();
// For freehand with dynamics
tools::DynamicsOptions getDynamics();
const tools::DynamicsOptions& getDynamics() const;
// Signals
obs::signal<void()> BrushChange;

View File

@ -15,8 +15,8 @@
#include "app/extensions.h"
#include "app/modules/palettes.h"
#include "app/ui/skin/skin_theme.h"
#include "app/util/conversion_to_surface.h"
#include "base/bind.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/image_ref.h"
#include "doc/primitives.h"
@ -113,8 +113,8 @@ private:
}
m_preview = os::instance()->createRgbaSurface(w, h);
doc::convert_image_to_surface(image2.get(), palette, m_preview,
0, 0, 0, 0, w, h);
convert_image_to_surface(image2.get(), palette, m_preview,
0, 0, 0, 0, w, h);
m_palId = palette->id();
m_palMods = palette->getModifications();

View File

@ -121,6 +121,7 @@ void BrushPreview::show(const gfx::Point& screenPos)
tools::Ink* ink = m_editor->getCurrentEditorInk();
const bool isFloodfill = m_editor->getCurrentEditorTool()->getPointShape(0)->isFloodFill();
const auto& dynamics = App::instance()->contextBar()->getDynamics();
// Setup the cursor type depending on several factors (current tool,
// foreground color, layer transparency, brush size, etc.).
@ -128,6 +129,16 @@ void BrushPreview::show(const gfx::Point& screenPos)
color_t brush_color = getBrushColor(sprite, layer);
color_t mask_index = sprite->transparentColor();
if (brush->type() != doc::kImageBrushType &&
(dynamics.size != tools::DynamicSensor::Static ||
dynamics.angle != tools::DynamicSensor::Static)) {
brush.reset(
new Brush(
brush->type(),
(dynamics.size != tools::DynamicSensor::Static ? dynamics.minSize: brush->size()),
(dynamics.angle != tools::DynamicSensor::Static ? dynamics.minAngle: brush->angle())));
}
if (ink->isSelection() || ink->isSlice()) {
m_type = SELECTION_CROSSHAIR;
}

View File

@ -51,12 +51,12 @@
#include "app/ui/timeline/timeline.h"
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "app/util/conversion_to_surface.h"
#include "app/util/layer_utils.h"
#include "base/bind.h"
#include "base/chrono.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "doc/conversion_to_surface.h"
#include "doc/doc.h"
#include "doc/mask_boundaries.h"
#include "doc/slice.h"

View File

@ -35,6 +35,7 @@
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/layer_utils.h"
#include "base/bind.h"
#include "base/gcd.h"
#include "base/pi.h"
@ -298,6 +299,9 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
getTransformation(editor));
if (handle != NoHandle) {
if (layer_is_locked(editor))
return true;
// Re-catch the image
m_pixelsMovement->catchImageAgain(
editor->screenToEditor(msg->position()), handle);
@ -311,6 +315,9 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
// right-click can be used to deselect/subtract selection, so we
// should drop the selection in this later case.
if (editor->isInsideSelection() && msg->left()) {
if (layer_is_locked(editor))
return true;
// In case that the user is pressing the copy-selection keyboard shortcut.
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
if ((customization) &&
@ -532,9 +539,22 @@ void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
if (!isActiveEditor())
return;
if (layer_is_locked(m_editor) &&
(command->id() == CommandId::Flip() ||
command->id() == CommandId::Cut() ||
command->id() == CommandId::Clear() ||
command->id() == CommandId::Rotate())) {
ev.cancel();
return;
}
// We don't need to drop the pixels if a MoveMaskCommand of Content is executed.
if (MoveMaskCommand* moveMaskCmd = dynamic_cast<MoveMaskCommand*>(command)) {
if (moveMaskCmd->getTarget() == MoveMaskCommand::Content) {
if (layer_is_locked(m_editor)) {
ev.cancel();
return;
}
gfx::Point delta = moveMaskCmd->getMoveThing().getDelta(UIContext::instance());
// Verify Shift condition of the MoveMaskCommand (i.e. wrap = true)
if (moveMaskCmd->isWrap()) {

View File

@ -588,6 +588,14 @@ void PixelsMovement::stampImage(bool finalStamp)
cels.push_back(currentCel);
}
if (currentCel && currentCel->layer() &&
currentCel->layer()->isImage() &&
!currentCel->layer()->isEditableHierarchy()) {
Transformation initialCelPos(gfx::Rect(m_initialMask0->bounds()));
redrawExtraImage(&initialCelPos);
stampExtraCelImage();
}
for (Cel* target : cels) {
// We'll re-create the transformation for the other cels
if (target != currentCel) {
@ -995,14 +1003,15 @@ CelList PixelsMovement::getEditableCels()
// TODO This case is used in paste too, where the cel() can be
// nullptr (e.g. we paste the clipboard image into an empty
// cel).
cels.push_back(m_site.cel());
if (m_site.layer() && m_site.layer()->isEditableHierarchy())
cels.push_back(m_site.cel());
return cels;
}
// Current cel (m_site.cel()) can be nullptr when we paste in an
// empty cel (Ctrl+V) and cut (Ctrl+X) the floating pixels.
if (m_site.cel() &&
m_site.cel()->layer()->isEditable()) {
m_site.cel()->layer()->isEditableHierarchy()) {
auto it = std::find(cels.begin(), cels.end(), m_site.cel());
if (it != cels.end())
cels.erase(it);

View File

@ -18,6 +18,7 @@
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/doc_range.h"
#include "app/i18n/strings.h"
#include "app/ini_file.h"
#include "app/pref/preferences.h"
#include "app/tools/active_tool.h"
@ -47,6 +48,7 @@
#include "app/ui/status_bar.h"
#include "app/ui/timeline/timeline.h"
#include "app/ui_context.h"
#include "app/util/layer_utils.h"
#include "app/util/new_image_from_mask.h"
#include "app/util/readable_time.h"
#include "base/bind.h"
@ -197,7 +199,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
}
else if (!layer->isMovable() || !layer->isEditableHierarchy()) {
StatusBar::instance()->showTip(
1000, fmt::format("Layer '{}' is locked", layer->name()));
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name()));
}
else {
MovingCelCollect collect(editor, layer);
@ -300,12 +302,6 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
int x, y, opacity;
Image* image = site.image(&x, &y, &opacity);
if (layer && image) {
if (!layer->isEditableHierarchy()) {
StatusBar::instance()->showTip(
1000, fmt::format("Layer '{}' is locked", layer->name()));
return true;
}
// Change to MovingPixelsState
transformSelection(editor, msg, handle);
}
@ -321,12 +317,6 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
// Move selected pixels
if (layer && editor->canStartMovingSelectionPixels() && msg->left()) {
if (!layer->isEditableHierarchy()) {
StatusBar::instance()->showTip(
1000, fmt::format("Layer '{}' is locked", layer->name()));
return true;
}
// Change to MovingPixelsState
transformSelection(editor, msg, MovePixelsHandle);
return true;
@ -759,6 +749,9 @@ void StandbyState::transformSelection(Editor* editor, MouseMessage* msg, HandleT
return;
}
if (layer_is_locked(editor))
return;
try {
// Clear brush preview, as the extra cel will be replaced with the
// transformed image.

View File

@ -42,6 +42,7 @@
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/expand_cel_canvas.h"
#include "app/util/layer_utils.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/layer.h"
@ -736,9 +737,7 @@ tools::ToolLoop* create_tool_loop(
return nullptr;
}
// If the active layer is read-only.
else if (!layer->isEditableHierarchy()) {
StatusBar::instance()->showTip(
1000, fmt::format("Layer '{}' is locked", layer->name()));
else if (layer_is_locked(editor)) {
return nullptr;
}
// If the active layer is reference.

View File

@ -19,11 +19,11 @@
#include "app/ui/search_entry.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui_context.h"
#include "app/util/conversion_to_surface.h"
#include "app/util/freetype_utils.h"
#include "base/bind.h"
#include "base/fs.h"
#include "base/string.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/image_ref.h"
#include "os/surface.h"

View File

@ -26,11 +26,11 @@
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/conversion_to_surface.h"
#include "app/util/pal_ops.h"
#include "base/bind.h"
#include "base/clamp.h"
#include "base/convert_to.h"
#include "doc/conversion_to_surface.h"
#include "doc/image.h"
#include "doc/layer_tilemap.h"
#include "doc/palette.h"
@ -293,8 +293,8 @@ public:
int w = tileImage->width();
int h = tileImage->height();
os::Surface* surface = os::instance()->createRgbaSurface(w, h);
doc::convert_image_to_surface(tileImage.get(), get_current_palette(),
surface, 0, 0, 0, 0, w, h);
convert_image_to_surface(tileImage.get(), get_current_palette(),
surface, 0, 0, 0, 0, w, h);
g->drawRgbaSurface(surface, gfx::Rect(0, 0, w, h), box);
surface->dispose();
}

View File

@ -393,10 +393,17 @@ void Timeline::detachDocument()
m_thumbnailsPrefConn.disconnect();
m_document->remove_observer(this);
m_document = nullptr;
m_sprite = nullptr;
m_layer = nullptr;
}
// Reset all pointers to this document, even DocRanges, we don't
// want to store a pointer to a layer of a document that we are not
// observing anymore (because the document might be deleted soon).
m_sprite = nullptr;
m_layer = nullptr;
m_range.clearRange();
m_startRange.clearRange();
m_dropRange.clearRange();
if (m_editor) {
m_editor->remove_observer(this);
m_editor = nullptr;
@ -1057,7 +1064,7 @@ bool Timeline::onProcessMessage(Message* msg)
// we shouldn't change the hot (so the separator can be
// tracked to the mouse's released).
if (m_clk.part == PART_SEPARATOR) {
m_separator_x = std::max(0, mousePos.x);
setSeparatorX(mousePos.x);
layout();
return true;
}
@ -1516,13 +1523,15 @@ void Timeline::onResize(ui::ResizeEvent& ev)
{
gfx::Rect rc = ev.bounds();
setBoundsQuietly(rc);
setSeparatorX(m_separator_x);
gfx::Size sz = m_aniControls.sizeHint();
m_aniControls.setBounds(
gfx::Rect(
rc.x,
rc.y+(visibleTagBands()-1)*oneTagHeight(),
std::min(sz.w, m_separator_x),
(!m_sprite || m_sprite->tags().empty() ? std::min(sz.w, rc.w):
std::min(sz.w, m_separator_x)),
oneTagHeight()));
updateScrollBars();
@ -2965,6 +2974,8 @@ void Timeline::regenerateRows()
void Timeline::regenerateTagBands()
{
const bool oldEmptyTagBand = m_tagBand.empty();
// TODO improve this implementation
std::vector<unsigned char> tagsPerFrame(m_sprite->totalFrames(), 0);
std::vector<Tag*> bands(4, nullptr);
@ -3002,8 +3013,13 @@ void Timeline::regenerateTagBands()
if (m_tagFocusBand >= m_tagBands)
m_tagFocusBand = -1;
if (oldVisibleBands != visibleTagBands())
if (oldVisibleBands != visibleTagBands() ||
// This case is to re-layout the timeline when the AniControl
// can use more/less space because there weren't tags and now
// there tags, or viceversa.
oldEmptyTagBand != m_tagBand.empty()) {
layout();
}
}
int Timeline::visibleTagBands() const
@ -4217,4 +4233,9 @@ void Timeline::setLayerCollapsedFlag(const layer_t l, const bool state)
}
}
void Timeline::setSeparatorX(int newValue)
{
m_separator_x = base::clamp(newValue, headerBoxWidth(), bounds().w-guiscale());
}
} // namespace app

View File

@ -357,6 +357,8 @@ namespace app {
void setLayerContinuousFlag(const layer_t layer, const bool state);
void setLayerCollapsedFlag(const layer_t layer, const bool state);
void setSeparatorX(int newValue);
ui::ScrollBar m_hbar;
ui::ScrollBar m_vbar;
gfx::Rect m_viewportArea;

View File

@ -1,14 +1,15 @@
// Aseprite Document Library
// Aseprite
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/conversion_to_surface.h"
#include "app/util/conversion_to_surface.h"
#include "base/24bits.h"
#include "doc/algo.h"
@ -22,7 +23,9 @@
#include <algorithm>
#include <stdexcept>
namespace doc {
namespace app {
using namespace doc;
namespace {
@ -131,8 +134,14 @@ void convert_image_to_surface_selector(const Image* image, os::Surface* surface,
} // anonymous namespace
void convert_image_to_surface(const Image* image, const Palette* palette,
os::Surface* surface, int src_x, int src_y, int dst_x, int dst_y, int w, int h)
void convert_image_to_surface(
const doc::Image* image,
const doc::Palette* palette,
os::Surface* surface,
int src_x, int src_y,
int dst_x, int dst_y,
int w, int h)
{
gfx::Rect srcBounds(src_x, src_y, w, h);
srcBounds = srcBounds.createIntersection(image->bounds());
@ -198,4 +207,4 @@ void convert_image_to_surface(const Image* image, const Palette* palette,
}
}
} // namespace doc
} // namespace app

View File

@ -0,0 +1,33 @@
// Aseprite
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2001-2014 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_CONVERSION_TO_SURFACE_H_INCLUDED
#define APP_UTIL_CONVERSION_TO_SURFACE_H_INCLUDED
#pragma once
namespace doc {
class Image;
class Palette;
}
namespace os {
class Surface;
}
namespace app {
void convert_image_to_surface(
const doc::Image* image,
const doc::Palette* palette,
os::Surface* surface,
int src_x, int src_y,
int dst_x, int dst_y,
int w, int h);
} // namespace app
#endif

View File

@ -6,8 +6,12 @@
#include "app/util/layer_utils.h"
#include "app/i18n/strings.h"
#include "app/ui/editor/editor.h"
#include "app/ui/status_bar.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "fmt/format.h"
namespace app {
@ -38,4 +42,18 @@ Layer* candidate_if_layer_is_deleted(
return const_cast<Layer*>(layerToSelect);
}
bool layer_is_locked(Editor* editor)
{
Layer* layer = editor->layer();
if (layer && !layer->isEditableHierarchy()) {
#ifdef ENABLE_UI
if (auto statusBar = StatusBar::instance())
statusBar->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name()));
#endif
return true;
}
return false;
}
} // namespace app

View File

@ -14,6 +14,8 @@ namespace doc {
namespace app {
class Editor;
// Calculates a possible candidate to be selected in case that we
// have a specific "selectedLayer" and are going to delete the given
// "layerToDelete".
@ -21,6 +23,10 @@ namespace app {
const doc::Layer* selectedLayer,
const doc::Layer* layerToDelete);
// True if the active layer is locked (itself or its hierarchy),
// also, it sends a tip to the user 'Layer ... is locked'
bool layer_is_locked(Editor* editor);
} // namespace app
#endif

View File

@ -39,7 +39,7 @@ static CelList get_cels_templ(const Sprite* sprite,
for (Layer* layer : range.selectedLayers()) {
if (!layer ||
!layer->isImage() ||
(onlyUnlockedCel && !layer->isEditable()))
(onlyUnlockedCel && !layer->isEditableHierarchy()))
continue;
LayerImage* layerImage = static_cast<LayerImage*>(layer);

@ -1 +1 @@
Subproject commit 8b026c048d775a2e3139e1059636fc5e65ef45b1
Subproject commit e6a1f9bf69480882cf9ee8df2b3a5f361238b691

View File

@ -31,7 +31,6 @@ add_library(doc-lib
cels_range.cpp
color.cpp
compressed_image.cpp
conversion_to_surface.cpp
document.cpp
file/act_file.cpp
file/col_file.cpp
@ -76,10 +75,7 @@ add_library(doc-lib
tilesets.cpp
user_data_io.cpp)
# TODO Remove 'os' as dependency and move conversion_to_surface.cpp/h files
# to other library/layer (render-lib? new conversion-lib?)
target_link_libraries(doc-lib
laf-os
laf-gfx
fixmath-lib
laf-base)
laf-base
fixmath-lib)

View File

@ -1,25 +0,0 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_CONVERSION_TO_SURFACE_H_INCLUDED
#define DOC_CONVERSION_TO_SURFACE_H_INCLUDED
#pragma once
namespace os {
class Surface;
}
namespace doc {
class Image;
class Palette;
void convert_image_to_surface(const Image* image, const Palette* palette,
os::Surface* surface,
int src_x, int src_y, int dst_x, int dst_y, int w, int h);
} // namespace doc
#endif

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2018-2019 Igara Studio S.A.
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -53,7 +53,7 @@ namespace doc {
public:
inline address_t address(int x, int y) const {
return (address_t)(m_rows[y] + x / (Traits::pixels_per_byte == 0 ? 1 : Traits::pixels_per_byte));
return (address_t)(getLineAddress(y) + x / (Traits::pixels_per_byte == 0 ? 1 : Traits::pixels_per_byte));
}
ImageImpl(const ImageSpec& spec,
@ -222,15 +222,15 @@ namespace doc {
template<>
inline void ImageImpl<IndexedTraits>::clear(color_t color) {
std::fill(m_bits,
m_bits + width()*height(),
std::fill(getBitsAddress(),
getBitsAddress() + width()*height(),
color);
}
template<>
inline void ImageImpl<BitmapTraits>::clear(color_t color) {
std::fill(m_bits,
m_bits + BitmapTraits::getRowStrideBytes(width()) * height(),
std::fill(getBitsAddress(),
getBitsAddress() + BitmapTraits::getRowStrideBytes(width()) * height(),
(color ? 0xff: 0x00));
}
@ -240,7 +240,7 @@ namespace doc {
ASSERT(y >= 0 && y < height());
std::div_t d = std::div(x, 8);
return ((*(m_rows[y] + d.quot)) & (1<<d.rem)) ? 1: 0;
return ((*(getLineAddress(y) + d.quot)) & (1<<d.rem)) ? 1: 0;
}
template<>
@ -250,9 +250,9 @@ namespace doc {
std::div_t d = std::div(x, 8);
if (color)
(*(m_rows[y] + d.quot)) |= (1 << d.rem);
(*(getLineAddress(y) + d.quot)) |= (1 << d.rem);
else
(*(m_rows[y] + d.quot)) &= ~(1 << d.rem);
(*(getLineAddress(y) + d.quot)) &= ~(1 << d.rem);
}
template<>

View File

@ -71,7 +71,7 @@ LayerList SelectedLayers::toAllLayersList() const
for (Layer* layer = (*begin())->sprite()->firstLayer();
layer != nullptr;
layer = layer->getNext()) {
layer = layer->getNextInWholeHierarchy()) {
if (contains(layer))
output.push_back(layer);
}

@ -1 +1 @@
Subproject commit 5f44047529b003f976ff87bd48e56a8c5b42c394
Subproject commit 5cc35476177355e035d2ea4b8f7465bb1addb93c

@ -1 +1 @@
Subproject commit ccc2a2d80853671334f1c9f0b2ed771a22f77562
Subproject commit 62da6c4279528505a0fb0e7e31afb9b82cd5aea9

View File

@ -1,3 +1,4 @@
Copyright (c) 2020 Igara Studio S.A.
Copyright (c) 2016 David Capello
Permission is hereby granted, free of charge, to any person obtaining

View File

@ -1,4 +1,5 @@
// Aseprite Steam Wrapper
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -10,15 +11,59 @@
#include "steam/steam.h"
#include "base/convert_to.h"
#include "base/dll.h"
#include "base/fs.h"
#include "base/ints.h"
#include "base/launcher.h"
#include "base/log.h"
#include "base/string.h"
namespace steam {
typedef bool (*SteamAPI_Init_Func)();
typedef void (*SteamAPI_Shutdown_Func)();
typedef uint32_t HSteamPipe;
typedef uint32_t HSteamUser;
typedef uint32_t ScreenshotHandle;
struct ISteamScreenshots;
struct ISteamUtils;
enum {
// Last callback received from Steam client when it's Steam is closed
kSteamServersDisconnected = 103,
kSteamUndocumentedLastCallback = 1009,
// When a screenshot is ready in the library
kScreenshotReady = 2301,
};
struct CallbackMsg_t {
HSteamUser steamUser;
int callback;
uint8_t* pubParam;
int cubParam;
};
#ifdef __GNUC__
#define __cdecl
#endif
// Steam main API
typedef bool (__cdecl *SteamAPI_Init_Func)();
typedef void (__cdecl *SteamAPI_Shutdown_Func)();
typedef HSteamPipe (__cdecl *SteamAPI_GetHSteamPipe_Func)();
// Steam callbacks
typedef void (__cdecl *SteamAPI_ManualDispatch_Init_Func)();
typedef void (__cdecl *SteamAPI_ManualDispatch_RunFrame_Func)(HSteamPipe);
typedef bool (__cdecl *SteamAPI_ManualDispatch_GetNextCallback_Func)(HSteamPipe, CallbackMsg_t*);
typedef void (__cdecl *SteamAPI_ManualDispatch_FreeLastCallback_Func)(HSteamPipe);
// ISteamScreenshots
typedef ISteamScreenshots* (__cdecl *SteamAPI_SteamScreenshots_v003_Func)();
typedef ScreenshotHandle (__cdecl *SteamAPI_ISteamScreenshots_WriteScreenshot_Func)(ISteamScreenshots*, void*, uint32_t, int, int);
// ISteamUtils
typedef ISteamUtils* (__cdecl *SteamAPI_SteamUtils_v009_Func)();
typedef uint32_t (__cdecl *SteamAPI_ISteamUtils_GetAppID_Func)(ISteamUtils*);
#ifdef _WIN32
#ifdef _WIN64
@ -32,9 +77,11 @@ typedef void (*SteamAPI_Shutdown_Func)();
#define STEAM_API_DLL_FILENAME "libsteam_api.so"
#endif
#define GETPROC(name) base::get_dll_proc<name##_Func>(m_steamLib, #name)
class SteamAPI::Impl {
public:
Impl() : m_initialized(false) {
Impl() {
m_steamLib = base::load_dll(
base::join_path(base::get_file_path(base::get_app_path()),
STEAM_API_DLL_FILENAME));
@ -43,18 +90,34 @@ public:
return;
}
auto SteamAPI_Init = base::get_dll_proc<SteamAPI_Init_Func>(m_steamLib, "SteamAPI_Init");
auto SteamAPI_Init = GETPROC(SteamAPI_Init);
if (!SteamAPI_Init) {
LOG("STEAM: SteamAPI_Init not found...\n");
return;
}
// Call SteamAPI_Init() to connect to Steam
if (!SteamAPI_Init()) {
LOG("STEAM: Steam is not initialized...\n");
return;
}
LOG("STEAM: Steam initialized...\n");
// Get functions to dispatch callbacks manually
auto SteamAPI_ManualDispatch_Init = GETPROC(SteamAPI_ManualDispatch_Init);
SteamAPI_ManualDispatch_RunFrame = GETPROC(SteamAPI_ManualDispatch_RunFrame);
SteamAPI_ManualDispatch_GetNextCallback = GETPROC(SteamAPI_ManualDispatch_GetNextCallback);
SteamAPI_ManualDispatch_FreeLastCallback = GETPROC(SteamAPI_ManualDispatch_FreeLastCallback);
auto SteamAPI_GetHSteamPipe = GETPROC(SteamAPI_GetHSteamPipe);
if (SteamAPI_ManualDispatch_Init &&
SteamAPI_ManualDispatch_RunFrame &&
SteamAPI_ManualDispatch_GetNextCallback &&
SteamAPI_ManualDispatch_FreeLastCallback &&
SteamAPI_GetHSteamPipe) {
SteamAPI_ManualDispatch_Init();
m_pipe = SteamAPI_GetHSteamPipe();
}
LOG("STEAM: Steam initialized\n");
m_initialized = true;
}
@ -62,32 +125,132 @@ public:
if (!m_steamLib)
return;
auto SteamAPI_Shutdown = base::get_dll_proc<SteamAPI_Shutdown_Func>(m_steamLib, "SteamAPI_Shutdown");
auto SteamAPI_Shutdown = GETPROC(SteamAPI_Shutdown);
if (SteamAPI_Shutdown) {
LOG("STEAM: Steam shutdown...\n");
SteamAPI_Shutdown();
}
base::unload_dll(m_steamLib);
unloadLib();
}
bool initialized() const {
return m_initialized;
}
void runCallbacks() {
if (!m_pipe)
return;
ASSERT(SteamAPI_ManualDispatch_RunFrame);
ASSERT(SteamAPI_ManualDispatch_GetNextCallback);
ASSERT(SteamAPI_ManualDispatch_FreeLastCallback);
SteamAPI_ManualDispatch_RunFrame(m_pipe);
CallbackMsg_t msg;
if (SteamAPI_ManualDispatch_GetNextCallback(m_pipe, &msg)) {
//TRACEARGS("SteamAPI_ManualDispatch_GetNextCallback", msg.callback);
bool disconnected = false;
switch (msg.callback) {
case kSteamServersDisconnected:
case kSteamUndocumentedLastCallback:
disconnected = true;
break;
// When a screenshot is ready, we open the Steam library of screenshots
case kScreenshotReady: {
std::string url = "steam://open/screenshots/";
auto SteamAPI_SteamUtils_v009 = GETPROC(SteamAPI_SteamUtils_v009);
auto SteamAPI_ISteamUtils_GetAppID = GETPROC(SteamAPI_ISteamUtils_GetAppID);
if (SteamAPI_SteamUtils_v009 &&
SteamAPI_ISteamUtils_GetAppID) {
ISteamUtils* utils = SteamAPI_SteamUtils_v009();
if (utils) {
int appId = SteamAPI_ISteamUtils_GetAppID(utils);
url += base::convert_to<std::string>(appId);
}
}
base::launcher::open_url(url);
break;
}
}
SteamAPI_ManualDispatch_FreeLastCallback(m_pipe);
// If the Steam client is closed, we have to unload the DLL and
// don't use the pipe or any Steam API at all, in other case we
// would crash.
if (disconnected) {
LOG("STEAM: Disconnected\n");
unloadLib();
}
}
}
bool writeScreenshot(void* rgbBuffer,
uint32_t sizeInBytes,
int width, int height) {
if (!m_initialized)
return false;
auto SteamScreenshots = GETPROC(SteamAPI_SteamScreenshots_v003);
auto WriteScreenshot = GETPROC(SteamAPI_ISteamScreenshots_WriteScreenshot);
if (!SteamScreenshots || !WriteScreenshot) {
LOG("STEAM: Error getting Steam Screenshot API functions\n");
return false;
}
auto screenshots = SteamScreenshots();
if (!screenshots) {
LOG("STEAM: Error getting Steam Screenshot API instance\n");
return false;
}
WriteScreenshot(screenshots, rgbBuffer, sizeInBytes, width, height);
return true;
}
private:
base::dll m_steamLib;
bool m_initialized;
void unloadLib() {
base::unload_dll(m_steamLib);
m_steamLib = nullptr;
m_initialized = false;
m_pipe = 0;
}
bool m_initialized = false;
base::dll m_steamLib = nullptr;
// To handle callbacks manually
HSteamPipe m_pipe = 0;
SteamAPI_ManualDispatch_RunFrame_Func SteamAPI_ManualDispatch_RunFrame = nullptr;
SteamAPI_ManualDispatch_GetNextCallback_Func SteamAPI_ManualDispatch_GetNextCallback = nullptr;
SteamAPI_ManualDispatch_FreeLastCallback_Func SteamAPI_ManualDispatch_FreeLastCallback = nullptr;
};
SteamAPI* g_instance = nullptr;
// static
SteamAPI* SteamAPI::instance()
{
return g_instance;
}
SteamAPI::SteamAPI()
: m_impl(new Impl)
{
ASSERT(g_instance == nullptr);
g_instance = this;
}
SteamAPI::~SteamAPI()
{
delete m_impl;
ASSERT(g_instance == this);
g_instance = nullptr;
}
bool SteamAPI::initialized() const
@ -95,4 +258,16 @@ bool SteamAPI::initialized() const
return m_impl->initialized();
}
void SteamAPI::runCallbacks()
{
m_impl->runCallbacks();
}
bool SteamAPI::writeScreenshot(void* rgbBuffer,
uint32_t sizeInBytes,
int width, int height)
{
return m_impl->writeScreenshot(rgbBuffer, sizeInBytes, width, height);
}
} // namespace steam

View File

@ -1,4 +1,5 @@
// Aseprite Steam Wrapper
// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -12,10 +13,17 @@ namespace steam {
class SteamAPI {
public:
static SteamAPI* instance();
SteamAPI();
~SteamAPI();
bool initialized() const;
void runCallbacks();
bool writeScreenshot(void* rgbBuffer,
uint32_t sizeInBytes,
int width, int height);
private:
class Impl;

@ -1 +1 @@
Subproject commit 1838abea0025288d636d3f367c692caae0f551d8
Subproject commit db4237e672d9e7370f074a273b49644588754d82

@ -1 +1 @@
Subproject commit cd8970dc2f6f2ad24604801c3a1d18cd67d7904f
Subproject commit 64bc33ab67c42330cc6cedd528c23eb33c445d64

View File

@ -33,6 +33,7 @@ if(NOT USE_SHARED_GIFLIB)
endif()
if(WITH_WEBP_SUPPORT)
set(WEBP_BUILD_EXTRAS OFF CACHE BOOL "Build extras.")
add_subdirectory(libwebp)
endif()