Merge branch 'master' into beta

This commit is contained in:
David Capello 2016-07-06 15:46:22 -03:00
commit 36ffc8d2bd
46 changed files with 1074 additions and 236 deletions

View File

@ -292,6 +292,7 @@
<section id="import_sprite_sheet">
<option id="type" type="app::SpriteSheetType" default="app::SpriteSheetType::Rows" />
<option id="bounds" type="gfx::Rect" default="gfx::Rect(0, 0, 16, 16)" />
<option id="partial_tiles" type="bool" default="false" />
</section>
<section id="preview" text="Preview">
<option id="zoom" type="double" default="1.0" />

View File

@ -1,5 +1,5 @@
<!-- ASEPRITE -->
<!-- Copyright (C) 2001-2015 by David Capello -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="import_sprite_sheet" text="Import Sprite Sheet">
<grid columns="4">
@ -18,6 +18,8 @@
<label text="Height" />
<entry id="height" text="16" maxsize="4" />
<check id="partial_tiles" text="Include partial tiles at bottom/right edges" cell_hspan="4" />
<hbox cell_hspan="4">
<boxfiller />
<hbox>

View File

@ -159,6 +159,12 @@ add_executable(aseprite WIN32
target_link_libraries(aseprite app-lib ${PLATFORM_LIBS})
add_dependencies(aseprite copy_data)
if(MSVC AND USE_SKIA_BACKEND)
# Add support to expand filename wildcards in argc/argv
set_target_properties(aseprite
PROPERTIES LINK_FLAGS "-LINK wsetargv.obj -ENTRY:\"wWinMainCRTStartup\"")
endif()
install(TARGETS aseprite
RUNTIME DESTINATION bin)

View File

@ -83,6 +83,7 @@ set(file_formats
file/jpeg_format.cpp
file/pcx_format.cpp
file/png_format.cpp
file/pixly_format.cpp
file/tga_format.cpp)
if(WITH_WEBP_SUPPORT)
list(APPEND file_formats file/webp_format.cpp)

View File

@ -230,6 +230,12 @@ void App::run()
she::instance()->activateApp();
#endif
#if _DEBUG
// On OS X, when we compile Aseprite on Debug mode, we're using it
// outside an app bundle, so we must active the app explicitly.
she::instance()->activateApp();
#endif
#ifdef ENABLE_UPDATER
// Launch the thread to check for updates.
app::CheckUpdateThreadLauncher checkUpdate(

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -74,25 +74,19 @@ void ChangeColorCommand::onExecute(Context* context)
case None:
// do nothing
break;
case IncrementIndex:
if (color.getType() == app::Color::IndexType) {
case IncrementIndex: {
int index = color.getIndex();
if (index < get_current_palette()->size()-1)
color = app::Color::fromIndex(index+1);
}
else
color = app::Color::fromIndex(0);
break;
case DecrementIndex:
if (color.getType() == app::Color::IndexType) {
}
case DecrementIndex: {
int index = color.getIndex();
if (index > 0)
color = app::Color::fromIndex(index-1);
}
else
color = app::Color::fromIndex(0);
break;
}
}
if (m_background)
colorbar->setBgColor(color);

View File

@ -117,6 +117,7 @@ void FlipCommand::onExecute(Context* context)
else
api.flipImage(image, flipBounds, m_flipType);
if (cel->layer()->isTransparent())
transaction.execute(new cmd::TrimCel(cel));
}
// When the mask is bigger than the cel bounds, we have to

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -92,6 +92,10 @@ public:
return (app::SpriteSheetType)(sheetType()->getSelectedItemIndex()+1);
}
bool partialTilesValue() const {
return partialTiles()->isSelected();
}
bool ok() const {
return closer() == import();
}
@ -214,6 +218,7 @@ private:
sheetType()->setSelectedItemIndex((int)app::SpriteSheetType::Rows-1);
onChangeRectangle(m_docPref->importSpriteSheet.bounds());
partialTiles()->setSelected(m_docPref->importSpriteSheet.partialTiles());
onEntriesChange();
}
}
@ -305,6 +310,7 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
Document* document = window.document();
DocumentPreferences* docPref = window.docPref();
gfx::Rect frameBounds = window.frameBounds();
bool partialTiles = window.partialTilesValue();
auto sheetType = window.sheetTypeValue();
ASSERT(document);
@ -321,21 +327,27 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
// Each sprite in the sheet
std::vector<gfx::Rect> tileRects;
int widthStop = sprite->width();
int heightStop = sprite->height();
if (partialTiles) {
widthStop += frameBounds.w-1;
heightStop += frameBounds.h-1;
}
switch (sheetType) {
case app::SpriteSheetType::Horizontal:
for (int x=frameBounds.x; x+frameBounds.w<=sprite->width(); x += frameBounds.w) {
for (int x=frameBounds.x; x+frameBounds.w<=widthStop; x += frameBounds.w) {
tileRects.push_back(gfx::Rect(x, frameBounds.y, frameBounds.w, frameBounds.h));
}
break;
case app::SpriteSheetType::Vertical:
for (int y=frameBounds.y; y+frameBounds.h<=sprite->height(); y += frameBounds.h) {
for (int y=frameBounds.y; y+frameBounds.h<=heightStop; y += frameBounds.h) {
tileRects.push_back(gfx::Rect(frameBounds.x, y, frameBounds.w, frameBounds.h));
}
break;
case app::SpriteSheetType::Rows:
for (int y=frameBounds.y; y+frameBounds.h<=sprite->height(); y += frameBounds.h) {
for (int x=frameBounds.x; x+frameBounds.w<=sprite->width(); x += frameBounds.w) {
for (int y=frameBounds.y; y+frameBounds.h<=heightStop; y += frameBounds.h) {
for (int x=frameBounds.x; x+frameBounds.w<=widthStop; x += frameBounds.w) {
tileRects.push_back(gfx::Rect(x, y, frameBounds.w, frameBounds.h));
}
}
@ -411,6 +423,7 @@ void ImportSpriteSheetCommand::onExecute(Context* context)
if (docPref) {
docPref->importSpriteSheet.type(sheetType);
docPref->importSpriteSheet.bounds(frameBounds);
docPref->importSpriteSheet.partialTiles(partialTiles);
}
}
catch (...) {

View File

@ -30,9 +30,9 @@
#include "ui/graphics.h"
#include "ui/listitem.h"
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#include "ui/resize_event.h"
#include "ui/separator.h"
#include "ui/size_hint_event.h"
#include "keyboard_shortcuts.xml.h"
@ -46,6 +46,20 @@ using namespace skin;
static int g_sep = 0;
class KeyItem : public ListItem {
// Used to avoid deleting the Add/Change/Del buttons on
// kMouseLeaveMessage when a foreground window is popup on a signal
// generated by those same buttons.
struct LockButtons {
KeyItem* keyItem;
LockButtons(KeyItem* keyItem) : keyItem(keyItem) {
keyItem->m_lockButtons = true;
};
~LockButtons() {
keyItem->m_lockButtons = false;
};
};
public:
KeyItem(const std::string& text, Key* key, AppMenuItem* menuitem, int level)
: ListItem(text)
@ -53,7 +67,8 @@ public:
, m_keyOrig(key ? new Key(*key): NULL)
, m_menuitem(menuitem)
, m_level(level)
, m_hotAccel(-1) {
, m_hotAccel(-1)
, m_lockButtons(false) {
gfx::Border border = this->border();
border.top(0);
border.bottom(0);
@ -73,6 +88,7 @@ public:
private:
void onChangeAccel(int index) {
LockButtons lock(this);
Accelerator origAccel = m_key->accels()[index];
SelectAccelerator window(origAccel, m_key->keycontext());
window.openWindowInForeground();
@ -87,6 +103,7 @@ private:
}
void onDeleteAccel(int index) {
LockButtons lock(this);
// We need to create a copy of the accelerator because
// Key::disableAccel() will modify the accels() collection itself.
ui::Accelerator accel = m_key->accels()[index];
@ -103,6 +120,7 @@ private:
}
void onAddAccel() {
LockButtons lock(this);
ui::Accelerator accel;
SelectAccelerator window(accel, m_key ? m_key->keycontext(): KeyContext::Any);
window.openWindowInForeground();
@ -221,13 +239,15 @@ private:
if (m_hotAccel != i) {
m_hotAccel = i;
m_changeConn = base::Connection();
m_changeButton.reset(new Button(""));
m_changeButton->Click.connect(base::Bind<void>(&KeyItem::onChangeAccel, this, i));
m_changeConn = m_changeButton->Click.connect(base::Bind<void>(&KeyItem::onChangeAccel, this, i));
setup_mini_look(m_changeButton.get());
addChild(m_changeButton.get());
m_deleteConn = base::Connection();
m_deleteButton.reset(new Button(""));
m_deleteButton->Click.connect(base::Bind<void>(&KeyItem::onDeleteAccel, this, i));
m_deleteConn = m_deleteButton->Click.connect(base::Bind<void>(&KeyItem::onDeleteAccel, this, i));
setup_mini_look(m_deleteButton.get());
addChild(m_deleteButton.get());
@ -251,8 +271,9 @@ private:
if (i == 0 && !m_addButton &&
(!m_menuitem || m_menuitem->getCommand())) {
m_addConn = base::Connection();
m_addButton.reset(new Button(""));
m_addButton->Click.connect(base::Bind<void>(&KeyItem::onAddAccel, this));
m_addConn = m_addButton->Click.connect(base::Bind<void>(&KeyItem::onAddAccel, this));
setup_mini_look(m_addButton.get());
addChild(m_addButton.get());
@ -273,9 +294,22 @@ private:
}
void destroyButtons() {
m_changeConn = base::Connection();
m_deleteConn = base::Connection();
m_addConn = base::Connection();
if (!m_lockButtons) {
m_changeButton.reset();
m_deleteButton.reset();
m_addButton.reset();
}
// Just hide the buttons
else {
if (m_changeButton) m_changeButton->setVisible(false);
if (m_deleteButton) m_deleteButton->setVisible(false);
if (m_addButton) m_addButton->setVisible(false);
}
m_hotAccel = -1;
}
@ -287,7 +321,11 @@ private:
base::SharedPtr<ui::Button> m_changeButton;
base::SharedPtr<ui::Button> m_deleteButton;
base::SharedPtr<ui::Button> m_addButton;
base::ScopedConnection m_changeConn;
base::ScopedConnection m_deleteConn;
base::ScopedConnection m_addConn;
int m_hotAccel;
bool m_lockButtons;
};
class KeyboardShortcutsWindow : public app::gen::KeyboardShortcuts {

View File

@ -12,6 +12,7 @@
#include "app/commands/filters/filter_manager_impl.h"
#include "app/cmd/copy_rect.h"
#include "app/cmd/patch_cel.h"
#include "app/cmd/unlink_cel.h"
#include "app/context_access.h"
#include "app/document.h"
@ -19,6 +20,7 @@
#include "app/modules/editors.h"
#include "app/transaction.h"
#include "app/ui/editor/editor.h"
#include "doc/algorithm/shrink_bounds.h"
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/images_collector.h"
@ -44,29 +46,23 @@ FilterManagerImpl::FilterManagerImpl(Context* context, Filter* filter)
: m_context(context)
, m_site(context->activeSite())
, m_filter(filter)
, m_dst(NULL)
, m_preview_mask(NULL)
, m_cel(nullptr)
, m_src(nullptr)
, m_dst(nullptr)
, m_mask(nullptr)
, m_previewMask(nullptr)
, m_progressDelegate(NULL)
{
int offset_x, offset_y;
m_src = NULL;
m_row = 0;
m_offset_x = 0;
m_offset_y = 0;
m_mask = NULL;
m_targetOrig = TARGET_ALL_CHANNELS;
m_target = TARGET_ALL_CHANNELS;
Image* image = m_site.image(&offset_x, &offset_y);
if (image == NULL)
int x, y;
Image* image = m_site.image(&x, &y);
if (!image)
throw NoImageException();
init(m_site.layer(), image, offset_x, offset_y);
}
FilterManagerImpl::~FilterManagerImpl()
{
init(m_site.cel());
}
app::Document* FilterManagerImpl::document()
@ -100,9 +96,8 @@ void FilterManagerImpl::begin()
Document* document = static_cast<app::Document*>(m_site.document());
m_row = 0;
m_mask = (document->isMaskVisible() ? document->mask(): NULL);
updateMask(m_mask, m_src);
m_mask = (document->isMaskVisible() ? document->mask(): nullptr);
updateBounds(m_mask);
}
void FilterManagerImpl::beginForPreview()
@ -110,17 +105,14 @@ void FilterManagerImpl::beginForPreview()
Document* document = static_cast<app::Document*>(m_site.document());
if (document->isMaskVisible())
m_preview_mask.reset(new Mask(*document->mask()));
m_previewMask.reset(new Mask(*document->mask()));
else {
m_preview_mask.reset(new Mask());
m_preview_mask->replace(
gfx::Rect(m_offset_x, m_offset_y,
m_src->width(),
m_src->height()));
m_previewMask.reset(new Mask());
m_previewMask->replace(m_site.sprite()->bounds());
}
m_row = 0;
m_mask = m_preview_mask;
m_mask = m_previewMask;
{
Editor* editor = current_editor;
@ -130,16 +122,16 @@ void FilterManagerImpl::beginForPreview()
vp = vp.createIntersection(sprite->bounds());
if (vp.isEmpty()) {
m_preview_mask.reset(NULL);
m_previewMask.reset(nullptr);
m_row = -1;
return;
}
m_preview_mask->intersect(vp);
m_previewMask->intersect(vp);
}
if (!updateMask(m_mask, m_src)) {
m_preview_mask.reset(NULL);
if (!updateBounds(m_mask)) {
m_previewMask.reset(nullptr);
m_row = -1;
return;
}
@ -152,19 +144,19 @@ void FilterManagerImpl::end()
bool FilterManagerImpl::applyStep()
{
if (m_row < 0 || m_row >= m_h)
if (m_row < 0 || m_row >= m_bounds.h)
return false;
if ((m_mask) && (m_mask->bitmap())) {
int x = m_x - m_mask->bounds().x + m_offset_x;
int y = m_y - m_mask->bounds().y + m_offset_y + m_row;
if ((m_w - x < 1) || (m_h - y < 1))
if (m_mask && m_mask->bitmap()) {
int x = m_bounds.x - m_mask->bounds().x;
int y = m_bounds.y - m_mask->bounds().y + m_row;
if ((x >= m_bounds.w) ||
(y >= m_bounds.h))
return false;
m_maskBits = m_mask->bitmap()
->lockBits<BitmapTraits>(Image::ReadLock,
gfx::Rect(x, y, m_w - x, m_h - y));
gfx::Rect(x, y, m_bounds.w - x, m_bounds.h - y));
m_maskIterator = m_maskBits.begin();
}
@ -187,7 +179,7 @@ void FilterManagerImpl::apply(Transaction& transaction)
while (!cancelled && applyStep()) {
if (m_progressDelegate) {
// Report progress.
m_progressDelegate->reportProgress(m_progressBase + m_progressWidth * (m_row+1) / m_h);
m_progressDelegate->reportProgress(m_progressBase + m_progressWidth * (m_row+1) / m_bounds.h);
// Does the user cancelled the whole process?
cancelled = m_progressDelegate->isCancelled();
@ -195,9 +187,16 @@ void FilterManagerImpl::apply(Transaction& transaction)
}
if (!cancelled) {
// Copy "dst" to "src"
transaction.execute(new cmd::CopyRect(
m_src, m_dst, gfx::Clip(m_x, m_y, m_x, m_y, m_w, m_h)));
gfx::Rect output;
if (algorithm::shrink_bounds2(m_src.get(), m_dst.get(),
m_bounds, output)) {
// Patch "m_cel"
transaction.execute(
new cmd::PatchCel(
m_cel, m_dst.get(),
gfx::Region(output),
position()));
}
}
}
@ -233,8 +232,7 @@ void FilterManagerImpl::applyToTarget()
// Avoid applying the filter two times to the same image
if (visited.find(image->id()) == visited.end()) {
visited.insert(image->id());
applyToImage(transaction, it->layer(),
image, it->cel()->x(), it->cel()->y());
applyToCel(transaction, it->cel());
}
// Is there a delegate to know if the process was cancelled by the user?
@ -255,10 +253,10 @@ void FilterManagerImpl::flush()
gfx::Rect rect(
editor->editorToScreen(
gfx::Point(
m_x+m_offset_x,
m_y+m_offset_y+m_row-1)),
m_bounds.x,
m_bounds.y+m_row-1)),
gfx::Size(
editor->projection().applyX(m_w),
editor->projection().applyX(m_bounds.w),
(editor->projection().scaleY() >= 1 ? editor->projection().applyY(1):
editor->projection().removeY(1))));
@ -273,12 +271,12 @@ void FilterManagerImpl::flush()
const void* FilterManagerImpl::getSourceAddress()
{
return m_src->getPixelAddress(m_x, m_row+m_y);
return m_src->getPixelAddress(m_bounds.x, m_bounds.y+m_row);
}
void* FilterManagerImpl::getDestinationAddress()
{
return m_dst->getPixelAddress(m_x, m_row+m_y);
return m_dst->getPixelAddress(m_bounds.x, m_bounds.y+m_row);
}
bool FilterManagerImpl::skipPixel()
@ -305,80 +303,48 @@ RgbMap* FilterManagerImpl::getRgbMap()
return m_site.sprite()->rgbMap(m_site.frame());
}
void FilterManagerImpl::init(const Layer* layer, Image* image, int offset_x, int offset_y)
void FilterManagerImpl::init(Cel* cel)
{
m_offset_x = offset_x;
m_offset_y = offset_y;
if (!updateMask(static_cast<app::Document*>(m_site.document())->mask(), image))
ASSERT(cel);
if (!updateBounds(static_cast<app::Document*>(m_site.document())->mask()))
throw InvalidAreaException();
m_src = image;
m_dst.reset(crop_image(image, 0, 0, image->width(), image->height(), 0));
m_cel = cel;
m_src.reset(
crop_image(
cel->image(),
gfx::Rect(m_site.sprite()->bounds()).offset(-cel->position()), 0));
m_dst.reset(Image::createCopy(m_src.get()));
m_row = -1;
m_mask = NULL;
m_preview_mask.reset(NULL);
m_mask = nullptr;
m_previewMask.reset(nullptr);
m_target = m_targetOrig;
/* the alpha channel of the background layer can't be modified */
if (layer->isBackground())
// The alpha channel of the background layer can't be modified
if (cel->layer()->isBackground())
m_target &= ~TARGET_ALPHA_CHANNEL;
}
void FilterManagerImpl::applyToImage(Transaction& transaction, Layer* layer, Image* image, int x, int y)
void FilterManagerImpl::applyToCel(Transaction& transaction, Cel* cel)
{
init(layer, image, x, y);
init(cel);
apply(transaction);
}
bool FilterManagerImpl::updateMask(Mask* mask, const Image* image)
bool FilterManagerImpl::updateBounds(doc::Mask* mask)
{
int x, y, w, h;
if (mask && mask->bitmap()) {
x = mask->bounds().x - m_offset_x;
y = mask->bounds().y - m_offset_y;
w = mask->bounds().w;
h = mask->bounds().h;
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x+w-1 >= image->width()-1)
w = image->width()-x;
if (y+h-1 >= image->height()-1)
h = image->height()-y;
gfx::Rect bounds;
if (mask && mask->bitmap() && !mask->bounds().isEmpty()) {
bounds = mask->bounds();
bounds &= m_site.sprite()->bounds();
}
else {
x = 0;
y = 0;
w = image->width();
h = image->height();
}
if ((w < 1) || (h < 1)) {
m_x = 0;
m_y = 0;
m_w = 0;
m_h = 0;
return false;
}
else {
m_x = x;
m_y = y;
m_w = w;
m_h = h;
return true;
bounds = m_site.sprite()->bounds();
}
m_bounds = bounds;
return !m_bounds.isEmpty();
}
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -12,14 +12,17 @@
#include "base/exception.h"
#include "base/unique_ptr.h"
#include "doc/image_impl.h"
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
#include "doc/site.h"
#include "filters/filter_indexed_data.h"
#include "filters/filter_manager.h"
#include "gfx/rect.h"
#include <cstring>
namespace doc {
class Cel;
class Image;
class Layer;
class Mask;
@ -67,7 +70,6 @@ namespace app {
};
FilterManagerImpl(Context* context, Filter* filter);
~FilterManagerImpl();
void setProgressDelegate(IProgressDelegate* progressDelegate);
@ -85,42 +87,43 @@ namespace app {
doc::Sprite* sprite() { return m_site.sprite(); }
doc::Layer* layer() { return m_site.layer(); }
doc::frame_t frame() { return m_site.frame(); }
doc::Image* destinationImage() const { return m_dst; }
doc::Image* destinationImage() const { return m_dst.get(); }
gfx::Point position() const { return gfx::Point(0, 0); }
// Updates the current editor to show the progress of the preview.
void flush();
// FilterManager implementation
const void* getSourceAddress();
void* getDestinationAddress();
int getWidth() { return m_w; }
Target getTarget() { return m_target; }
FilterIndexedData* getIndexedData() { return this; }
bool skipPixel();
const doc::Image* getSourceImage() { return m_src; }
int x() { return m_x; }
int y() { return m_y+m_row; }
const void* getSourceAddress() override;
void* getDestinationAddress() override;
int getWidth() override { return m_bounds.w; }
Target getTarget() override { return m_target; }
FilterIndexedData* getIndexedData() override { return this; }
bool skipPixel() override;
const doc::Image* getSourceImage() override { return m_src.get(); }
int x() override { return m_bounds.x; }
int y() override { return m_bounds.y+m_row; }
// FilterIndexedData implementation
doc::Palette* getPalette();
doc::RgbMap* getRgbMap();
doc::Palette* getPalette() override;
doc::RgbMap* getRgbMap() override;
private:
void init(const doc::Layer* layer, doc::Image* image, int offset_x, int offset_y);
void init(doc::Cel* cel);
void apply(Transaction& transaction);
void applyToImage(Transaction& transaction, doc::Layer* layer, doc::Image* image, int x, int y);
bool updateMask(doc::Mask* mask, const doc::Image* image);
void applyToCel(Transaction& transaction, doc::Cel* cel);
bool updateBounds(doc::Mask* mask);
Context* m_context;
doc::Site m_site;
Filter* m_filter;
doc::Image* m_src;
base::UniquePtr<doc::Image> m_dst;
doc::Cel* m_cel;
doc::ImageRef m_src;
doc::ImageRef m_dst;
int m_row;
int m_x, m_y, m_w, m_h;
int m_offset_x, m_offset_y;
gfx::Rect m_bounds;
doc::Mask* m_mask;
base::UniquePtr<doc::Mask> m_preview_mask;
base::UniquePtr<doc::Mask> m_previewMask;
doc::ImageBits<doc::BitmapTraits> m_maskBits;
doc::ImageBits<doc::BitmapTraits>::iterator m_maskIterator;
Target m_targetOrig; // Original targets

View File

@ -70,6 +70,7 @@ bool FilterPreview::onProcessMessage(Message* msg)
m_filterMgr->layer(),
m_filterMgr->frame(),
m_filterMgr->destinationImage(),
m_filterMgr->position(),
static_cast<doc::LayerImage*>(m_filterMgr->layer())->blendMode());
break;

View File

@ -9,6 +9,7 @@
#include "config.h"
#endif
#include "app/context.h"
#include "app/document.h"
#include "app/file/file.h"
#include "app/file/file_format.h"
@ -16,10 +17,12 @@
#include "base/cfile.h"
#include "base/exception.h"
#include "base/file_handle.h"
#include "base/path.h"
#include "doc/doc.h"
#include "ui/alert.h"
#include "zlib.h"
#include <stdio.h>
#include <cstdio>
#define ASE_FILE_MAGIC 0xA5E0
#define ASE_FILE_FRAME_MAGIC 0xF1FA
@ -132,6 +135,8 @@ static void ase_file_write_frame_tags_chunk(FILE* f, ASE_FrameHeader* frame_head
const frame_t fromFrame, const frame_t toFrame);
static void ase_file_read_user_data_chunk(FILE* f, UserData* userData);
static void ase_file_write_user_data_chunk(FILE* f, ASE_FrameHeader* frame_header, const UserData* userData);
static bool ase_has_groups(LayerFolder* layer);
static void ase_ungroup_all(LayerFolder* layer);
class ChunkWriter {
public:
@ -169,6 +174,7 @@ class AseFormat : public FileFormat {
}
bool onLoad(FileOp* fop) override;
bool onPostLoad(FileOp* fop) override;
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
@ -340,6 +346,34 @@ bool AseFormat::onLoad(FileOp* fop)
}
}
bool AseFormat::onPostLoad(FileOp* fop)
{
LayerFolder* folder = fop->document()->sprite()->folder();
// Forward Compatibility: In 1.1 we convert a file with layer groups
// (saved with 1.2) as top level layers
std::string ver = VERSION;
bool flat = (ver[0] == '1' &&
ver[1] == '.' &&
ver[2] == '1');
if (flat && ase_has_groups(folder)) {
if (fop->context() &&
fop->context()->isUIAvailable() &&
ui::Alert::show("Warning"
"<<The selected file \"%s\" has layer groups."
"<<Do you want to open it with \"%s %s\" anyway?"
"<<"
"<<Note: Layers inside groups will be converted to top level layers."
"||&Yes||&No",
base::get_file_name(fop->filename()).c_str(),
PACKAGE, ver.c_str()) != 1) {
return false;
}
ase_ungroup_all(folder);
}
return true;
}
#ifdef ENABLE_SAVE
bool AseFormat::onSave(FileOp* fop)
@ -1570,4 +1604,46 @@ static void ase_file_write_user_data_chunk(FILE* f, ASE_FrameHeader* frame_heade
}
}
static bool ase_has_groups(LayerFolder* folder)
{
for (Layer* child : folder->getLayersList()) {
if (child->isFolder())
return true;
}
return false;
}
static void ase_ungroup_all(LayerFolder* folder)
{
LayerFolder* root = folder->sprite()->folder();
LayerList list = folder->getLayersList();
for (Layer* child : list) {
if (child->isFolder()) {
ase_ungroup_all(static_cast<LayerFolder*>(child));
folder->removeLayer(child);
}
else if (folder != root) {
// Create a new name adding all group layer names
{
std::string name;
for (Layer* layer=child; layer!=root; layer=layer->parent()) {
if (!name.empty())
name.insert(0, "-");
name.insert(0, layer->name());
}
child->setName(name);
}
folder->removeLayer(child);
root->addLayer(child);
}
}
if (folder != root) {
ASSERT(folder->getLayersCount() == 0);
delete folder;
}
}
} // namespace app

View File

@ -309,6 +309,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
bool fatal = false;
// Check image type support
// TODO add support to automatically convert the image to a supported format
switch (fop->m_document->sprite()->pixelFormat()) {
case IMAGE_RGB:

View File

@ -21,6 +21,7 @@
namespace app {
extern FileFormat* CreateAseFormat();
extern FileFormat* CreatePixlyFormat();
extern FileFormat* CreateBmpFormat();
extern FileFormat* CreateFliFormat();
extern FileFormat* CreateGifFormat();
@ -55,6 +56,7 @@ FileFormatsManager::FileFormatsManager()
{
// The first format is the default image format in FileSelector
registerFormat(CreateAseFormat());
registerFormat(CreatePixlyFormat());
registerFormat(CreateBmpFormat());
registerFormat(CreateFliFormat());
registerFormat(CreateGifFormat());

View File

@ -0,0 +1,526 @@
// Aseprite
// Copyright (C) 2016 Carlo "zED" Caputo
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// Based on the code of David Capello
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/document.h"
#include "app/file/file.h"
#include "app/file/file_format.h"
#include "app/xml_document.h"
#include "base/file_handle.h"
#include "base/convert_to.h"
#include "base/path.h"
#include "doc/doc.h"
#include <cmath>
#include <cctype>
#include "png.h"
namespace app {
using namespace base;
class PixlyFormat : public FileFormat {
const char* onGetName() const override { return "anim"; }
const char* onGetExtensions() const override { return "anim"; }
int onGetFlags() const override {
return
FILE_SUPPORT_LOAD |
FILE_SUPPORT_SAVE |
FILE_SUPPORT_RGB |
FILE_SUPPORT_RGBA |
FILE_SUPPORT_LAYERS |
FILE_SUPPORT_FRAMES;
}
bool onLoad(FileOp* fop) override;
#ifdef ENABLE_SAVE
bool onSave(FileOp* fop) override;
#endif
};
FileFormat* CreatePixlyFormat()
{
return new PixlyFormat;
}
static void report_png_error(png_structp png_ptr, png_const_charp error)
{
((FileOp*)png_get_error_ptr(png_ptr))->setError("libpng: %s\n", error);
}
template<typename Any> static Any* check(Any* a, Any* alt = NULL) {
if(a == NULL) {
if(alt == NULL) {
throw Exception("bad structure");
} else {
return alt;
}
} else {
return a;
}
}
template<typename Number> static Number check_number(const char* c_str) {
if(c_str == NULL) {
throw Exception("value not found");
} else {
std::string str = c_str;
if(str.empty()) {
throw Exception("value empty");
}
std::string::const_iterator it = str.begin();
while (it != str.end() && (std::isdigit(*it) || *it == '.')) ++it;
if(it != str.end()) {
throw Exception("value not a number");
}
return base::convert_to<Number>(str);
}
}
bool PixlyFormat::onLoad(FileOp* fop)
{
png_uint_32 width, height, y;
unsigned int sig_read = 0;
png_structp png_ptr;
png_infop info_ptr;
int bit_depth, color_type, interlace_type;
int pass, number_passes;
png_bytepp rows_pointer;
PixelFormat pixelFormat;
FileHandle handle(open_file_with_exception(base::replace_extension(fop->filename(),"png"), "rb"));
FILE* fp = handle.get();
/* Create and initialize the png_struct with the desired error handler
* functions. If you want to use the default stderr and longjump method,
* you can supply NULL for the last three parameters. We also supply the
* the compiler header file version, so that we know if the application
* was compiled with a compatible version of the library
*/
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)fop,
report_png_error, report_png_error);
if (png_ptr == NULL) {
fop->setError("png_create_read_struct\n");
return false;
}
/* Allocate/initialize the memory for image information. */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
fop->setError("png_create_info_struct\n");
png_destroy_read_struct(&png_ptr, NULL, NULL);
return false;
}
/* Set error handling if you are using the setjmp/longjmp method (this is
* the normal method of doing things with libpng).
*/
if (setjmp(png_jmpbuf(png_ptr))) {
fop->setError("Error reading PNG file\n");
/* Free all of the memory associated with the png_ptr and info_ptr */
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
/* If we get here, we had a problem reading the file */
return false;
}
/* Set up the input control if you are using standard C streams */
png_init_io(png_ptr, fp);
/* If we have already read some of the signature */
png_set_sig_bytes(png_ptr, sig_read);
/* The call to png_read_info() gives us all of the information from the
* PNG file before the first IDAT (image data chunk).
*/
png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
&interlace_type, NULL, NULL);
/* Set up the data transformations you want. Note that these are all
* optional. Only call them if you want/need them. Many of the
* transformations only work on specific types of images, and many
* are mutually exclusive.
*/
/* tell libpng to strip 16 bit/color files down to 8 bits/color */
png_set_strip_16(png_ptr);
/* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
* byte into separate bytes (useful for paletted and grayscale images).
*/
png_set_packing(png_ptr);
/* Turn on interlace handling. REQUIRED if you are not using
* png_read_image(). To see how to handle interlacing passes,
* see the png_read_row() method below:
*/
number_passes = png_set_interlace_handling(png_ptr);
/* Optional call to gamma correct and add the background to the palette
* and update info structure.
*/
png_read_update_info(png_ptr, info_ptr);
/* create the output image */
switch (png_get_color_type(png_ptr, info_ptr)) {
case PNG_COLOR_TYPE_RGB_ALPHA:
fop->sequenceSetHasAlpha(true);
pixelFormat = IMAGE_RGB;
break;
default:
fop->setError("Pixly loader requires a RGBA PNG\n)");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return false;
}
int imageWidth = png_get_image_width(png_ptr, info_ptr);
int imageHeight = png_get_image_height(png_ptr, info_ptr);
// Allocate the memory to hold the image using the fields of info_ptr.
rows_pointer = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
for (y = 0; y < height; y++)
rows_pointer[y] = (png_bytep)png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
for (pass = 0; pass < number_passes; pass++) {
for (y = 0; y < height; y++) {
png_read_rows(png_ptr, rows_pointer+y, nullptr, 1);
fop->setProgress(
0.5 * ((double)((double)pass + (double)(y+1) / (double)(height))
/ (double)number_passes));
if (fop->isStop())
break;
}
}
bool success = true;
try {
XmlDocumentRef doc = open_xml(fop->filename());
TiXmlHandle xml(doc.get());
fop->setProgress(0.75);
TiXmlElement* xmlAnim = check(xml.FirstChild("PixlyAnimation").ToElement());
double version = check_number<double>(xmlAnim->Attribute("version"));
if(version < 1.5) {
throw Exception("version 1.5 or above required");
}
TiXmlElement* xmlInfo = check(xmlAnim->FirstChild("Info"))->ToElement();
int layerCount = check_number<int>(xmlInfo->Attribute("layerCount"));
int frameWidth = check_number<int>(xmlInfo->Attribute("frameWidth"));
int frameHeight = check_number<int>(xmlInfo->Attribute("frameHeight"));
UniquePtr<Sprite> sprite(new Sprite(IMAGE_RGB, frameWidth, frameHeight, 0));
TiXmlElement* xmlFrames = check(xmlAnim->FirstChild("Frames"))->ToElement();
int imageCount = check_number<int>(xmlFrames->Attribute("length"));
if(layerCount <= 0 || imageCount <= 0) {
throw Exception("No cels found");
}
int frameCount = imageCount / layerCount;
sprite->setTotalFrames(frame_t(frameCount));
sprite->setDurationForAllFrames(200);
for(int i=0; i<layerCount; i++) {
sprite->folder()->addLayer(new LayerImage(sprite));
}
std::vector<int> visible(layerCount, 0);
TiXmlElement* xmlFrame = check(xmlFrames->FirstChild("Frame"))->ToElement();
while (xmlFrame) {
TiXmlElement* xmlRegion = check(xmlFrame->FirstChild("Region"))->ToElement();
TiXmlElement* xmlIndex = check(xmlFrame->FirstChild("Index"))->ToElement();
int index = check_number<int>(xmlIndex->Attribute("linear"));
frame_t frame(index / layerCount);
LayerIndex layer_index(index % layerCount);
Layer *layer = sprite->indexToLayer(layer_index);
const char * duration = xmlFrame->Attribute("duration");
if(duration) {
sprite->setFrameDuration(frame, base::convert_to<int>(std::string(duration)));
}
visible[(int)layer_index] += (int)(std::string(check(xmlFrame->Attribute("visible"),"false")) == "true");
int x0 = check_number<int>(xmlRegion->Attribute("x"));
int y0 = check_number<int>(xmlRegion->Attribute("y")); // inverted
if(y0 < 0 || y0 + frameHeight > imageHeight || x0 < 0 || x0 + frameWidth > imageWidth) {
throw Exception("looking for cels outside the bounds of the PNG");
}
base::UniquePtr<Cel> cel;
ImageRef image(Image::create(pixelFormat, frameWidth, frameHeight));
// Convert rows_pointer into the doc::Image
for (int y = 0; y < frameHeight; y++) {
// RGB_ALPHA
uint8_t* src_address = rows_pointer[imageHeight-1 - y0 - (frameHeight-1) + y] + (x0 * 4);
uint32_t* dst_address = (uint32_t*)image->getPixelAddress(0, y);
unsigned int r, g, b, a;
for (int x=0; x<frameWidth; x++) {
r = *(src_address++);
g = *(src_address++);
b = *(src_address++);
a = *(src_address++);
*(dst_address++) = rgba(r, g, b, a);
}
}
cel.reset(new Cel(frame, image));
static_cast<LayerImage*>(layer)->addCel(cel);
cel.release();
xmlFrame = xmlFrame->NextSiblingElement();
fop->setProgress(0.75 + 0.25 * ((float)(index+1) / (float)imageCount));
}
for(int i=0; i<layerCount; i++) {
LayerIndex layer_index(i);
Layer *layer = sprite->indexToLayer(layer_index);
layer->setVisible(visible[i] > frameCount/2);
}
fop->createDocument(sprite);
sprite.release();
}
catch(Exception &e) {
fop->setError((std::string("Pixly file format: ")+std::string(e.what())+"\n").c_str());
success = false;
}
for (y = 0; y < height; y++) {
png_free(png_ptr, rows_pointer[y]);
}
png_free(png_ptr, rows_pointer);
// Clean up after the read, and free any memory allocated
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
return success;
}
#ifdef ENABLE_SAVE
bool PixlyFormat::onSave(FileOp* fop)
{
const Sprite* sprite = fop->document()->sprite();
auto it = sprite->folder()->getLayerBegin(),
end = sprite->folder()->getLayerEnd();
for (; it != end; ++it) { // layers
Layer *layer = *it;
if (!layer->isImage()) {
fop->setError("Pixly .anim file format does not support layer folders\n");
return false;
}
}
int width, height, y;
png_structp png_ptr;
png_infop info_ptr;
png_bytepp rows_pointer;
int color_type = 0;
/* open the file */
FileHandle xml_handle(open_file_with_exception(fop->filename(), "wb"));
FILE* xml_fp = xml_handle.get();
FileHandle handle(open_file_with_exception(base::replace_extension(fop->filename(),"png"), "wb"));
FILE* fp = handle.get();
/* Create and initialize the png_struct with the desired error handler
* functions. If you want to use the default stderr and longjump method,
* you can supply NULL for the last three parameters. We also check that
* the library version is compatible with the one used at compile time,
* in case we are using dynamically linked libraries. REQUIRED.
*/
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)fop,
report_png_error, report_png_error);
if (png_ptr == NULL) {
return false;
}
/* Allocate/initialize the image information data. REQUIRED */
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL) {
png_destroy_write_struct(&png_ptr, NULL);
return false;
}
/* Set error handling. REQUIRED if you aren't supplying your own
* error handling functions in the png_create_write_struct() call.
*/
if (setjmp(png_jmpbuf(png_ptr))) {
/* If we get here, we had a problem reading the file */
png_destroy_write_struct(&png_ptr, &info_ptr);
return false;
}
/* set up the output control if you are using standard C streams */
png_init_io(png_ptr, fp);
/* Set the image information here. Width and height are up to 2^31,
* bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
* the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
* PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
* or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
* PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
* currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
*/
int frameCount = sprite->totalFrames();
int layerCount = sprite->folder()->getLayersCount();
int imageCount = frameCount * layerCount;
int frameWidth = sprite->width();
int frameHeight = sprite->height();
int squareSide = (int)ceil(sqrt(imageCount));
width = squareSide * frameWidth;
height = squareSide * frameHeight;
color_type = PNG_COLOR_TYPE_RGB_ALPHA;
png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
/* Write the file header information. */
png_write_info(png_ptr, info_ptr);
/* pack pixels into bytes */
png_set_packing(png_ptr);
rows_pointer = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
for (y = 0; y < height; y++) {
size_t size = png_get_rowbytes(png_ptr, info_ptr);
rows_pointer[y] = (png_bytep)png_malloc(png_ptr, size);
memset(rows_pointer[y], 0, size);
fop->setProgress(0.1 * (double)(y+1) / (double)height);
}
// TODO XXX beware the required typo on Pixly xml: "totalCollumns" (sic)
fprintf(xml_fp,
"<PixlyAnimation version=\"1.5\">\n"
"\t<Info "
"sheetWidth=\"%d\" sheetHeight=\"%d\" "
"totalCollumns=\"%d\" totalRows=\"%d\" "
"frameWidth=\"%d\" frameHeight=\"%d\" "
"layerCount=\"%d\"/>\n"
"\t<Frames length=\"%d\">\n",
width, height,
squareSide, squareSide,
frameWidth, frameHeight,
layerCount, imageCount
);
int index = 0;
for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
auto it = sprite->folder()->getLayerBegin(),
end = sprite->folder()->getLayerEnd();
for (; it != end; ++it, ++index) { // layers
Layer *layer = *it;
int col = index % squareSide;
int row = index / squareSide;
int x0 = col * frameWidth;
int y0 = row * frameHeight; // inverted
int duration = sprite->frameDuration(frame);
// TODO XXX beware the required typo on Pixly xml: "collumn" (sic)
fprintf(xml_fp,
"\t\t<Frame duration=\"%d\" visible=\"%s\">\n"
"\t\t\t<Region x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"/>\n"
"\t\t\t<Index linear=\"%d\" collumn=\"%d\" row=\"%d\"/>\n"
"\t\t</Frame>\n",
duration, layer->isVisible() ? "true" : "false",
x0, y0, frameWidth, frameHeight,
index, col, row
);
const Cel* cel = layer->cel(frame);
if (cel) {
const Image* image = cel->image();
if (image) {
int celX = cel->x();
int celY = cel->y();
int celWidth = image->width();
int celHeight = image->height();
for (y = 0; y < celHeight; y++) {
/* RGB_ALPHA */
uint32_t* src_address = (uint32_t*)image->getPixelAddress(0, y);
uint8_t* dst_address = rows_pointer[(height - 1) - y0 - (frameHeight - 1) + celY + y] + ((x0 + celX) * 4);
int x;
unsigned int c;
for (x=0; x<celWidth; x++) {
c = *(src_address++);
*(dst_address++) = rgba_getr(c);
*(dst_address++) = rgba_getg(c);
*(dst_address++) = rgba_getb(c);
*(dst_address++) = rgba_geta(c);
} // x
} // y
} // image
} // cel
fop->setProgress(0.1 + 0.8 * (double)(index+1) / (double)imageCount);
} // layer
} // frame
fprintf(xml_fp,
"\t</Frames>\n"
"</PixlyAnimation>\n"
);
/* If you are only writing one row at a time, this works */
for (y = 0; y < height; y++) {
/* write the line */
png_write_rows(png_ptr, rows_pointer+y, 1);
fop->setProgress(0.9 + 0.1 * (double)(y+1) / (double)height);
}
for (y = 0; y < height; y++) {
png_free(png_ptr, rows_pointer[y]);
}
png_free(png_ptr, rows_pointer);
/* It is REQUIRED to call this to finish writing the rest of the file */
png_write_end(png_ptr, info_ptr);
/* clean up after the write, and free any memory allocated */
png_destroy_write_struct(&png_ptr, &info_ptr);
/* all right */
return true;
}
#endif
} // namespace app

View File

@ -12,10 +12,16 @@
#include "app/ini_file.h"
#include "app/resource_finder.h"
#include "base/path.h"
#include "base/split_string.h"
#include "base/string.h"
#include "cfg/cfg.h"
#ifdef __APPLE__
#include "she/logger.h"
#include "she/system.h"
#endif
#ifndef _WIN32
#include "base/fs.h"
#endif
@ -34,9 +40,48 @@ ConfigModule::ConfigModule()
{
ResourceFinder rf;
rf.includeUserDir("aseprite.ini");
// getFirstOrCreateDefault() will create the Aseprite directory
// inside the OS configuration folder (~/.config/aseprite/, etc.).
std::string fn = rf.getFirstOrCreateDefault();
#ifndef _WIN32 // Migrate the configuration file to the new location in Unix-like systems
#ifdef __APPLE__
// On OS X we migrate from ~/.config/aseprite/* -> "~/Library/Application Support/Aseprite/*"
if (!base::is_file(fn)) {
try {
std::string new_dir = base::get_file_path(fn);
// Now we try to move all old configuration files into the new
// directory.
ResourceFinder old_rf;
old_rf.includeHomeDir(".config/aseprite/aseprite.ini");
std::string old_config_fn = old_rf.defaultFilename();
if (base::is_file(old_config_fn)) {
std::string old_dir = base::get_file_path(old_config_fn);
for (std::string old_fn : base::list_files(old_dir)) {
std::string from = base::join_path(old_dir, old_fn);
std::string to = base::join_path(new_dir, old_fn);
base::move_file(from, to);
}
base::remove_directory(old_dir);
}
}
// Something failed
catch (const std::exception& ex) {
std::string err = "Error in configuration migration: ";
err += ex.what();
auto system = she::instance();
if (system && system->logger())
system->logger()->logError(err.c_str());
}
}
#elif !defined(_WIN32)
// On Linux we migrate the old configuration file name
// (.asepriterc -> ~/.config/aseprite/aseprite.ini)
{
ResourceFinder old_rf;
old_rf.includeHomeDir(".asepriterc");
@ -44,6 +89,7 @@ ConfigModule::ConfigModule()
if (base::is_file(old_fn))
base::move_file(old_fn, fn);
}
#endif
set_config_file(fn.c_str());

View File

@ -160,6 +160,14 @@ void ResourceFinder::includeUserDir(const char* filename)
includeHomeDir(filename);
}
#elif __APPLE__
// $HOME/Library/Application Support/Aseprite/filename
addPath(
base::join_path(
base::join_path(base::get_lib_app_support_path(), PACKAGE),
filename).c_str());
#else
// $HOME/.config/aseprite/filename

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
// Copyright (C) 2001-2016 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
@ -19,6 +19,7 @@ namespace app {
class PointShape {
public:
virtual ~PointShape() { }
virtual bool isPixel() { return false; }
virtual bool isFloodFill() { return false; }
virtual bool isSpray() { return false; }
virtual void preparePointShape(ToolLoop* loop) { }

View File

@ -21,6 +21,8 @@ public:
class PixelPointShape : public PointShape {
public:
bool isPixel() override { return true; }
void transformPoint(ToolLoop* loop, int x, int y) override {
doInkHline(x, y, x, loop);
}

View File

@ -465,6 +465,17 @@ class ContextBar::InkShadesField : public HBox {
parent()->parent()->layout();
}
void updateShadeFromColorBarPicks() {
auto colorBar = ColorBar::instance();
if (!colorBar)
return;
doc::PalettePicks picks;
colorBar->getPaletteView()->getSelectedEntries(picks);
if (picks.picks() >= 2)
onChangeColorBarSelection();
}
private:
void onChangeColorBarSelection() {
@ -710,6 +721,10 @@ public:
m_shade.setShade(shade);
}
void updateShadeFromColorBarPicks() {
m_shade.updateShadeFromColorBarPicks();
}
private:
void onShowMenu() {
loadShades();
@ -1553,6 +1568,8 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_spraySpeed->setValue(toolPref->spray.speed());
}
bool updateShade = (!m_inkShades->isVisible() && hasInkShades);
m_eyedropperField->updateFromPreferences(preferences.eyedropper);
m_autoSelectLayer->setSelected(preferences.editor.autoSelectLayer());
@ -1631,6 +1648,10 @@ void ContextBar::updateForTool(tools::Tool* tool)
(isPaint || isEffect || hasSelectOptions));
m_symmetry->updateWithCurrentDocument();
// Update ink shades with the current selected palette entries
if (updateShade)
m_inkShades->updateShadeFromColorBarPicks();
layout();
}

View File

@ -523,7 +523,8 @@ bool DocumentView::onClear(Context* ctx)
transaction.execute(new cmd::ClearMask(writer.cel()));
// If the cel wasn't deleted by cmd::ClearMask, we trim it.
if (writer.cel())
if (writer.cel() &&
writer.cel()->layer()->isTransparent())
transaction.execute(new cmd::TrimCel(writer.cel()));
if (visibleMask &&

View File

@ -305,17 +305,19 @@ void BrushPreview::generateBoundaries()
m_brushGen == brush->gen())
return;
bool isFloodfill = m_editor->getCurrentEditorTool()->getPointShape(0)->isFloodFill();
bool isOnePixel =
(m_editor->getCurrentEditorTool()->getPointShape(0)->isPixel() ||
m_editor->getCurrentEditorTool()->getPointShape(0)->isFloodFill());
Image* brushImage = brush->image();
int w = (isFloodfill ? 1: brushImage->width());
int h = (isFloodfill ? 1: brushImage->height());
int w = (isOnePixel ? 1: brushImage->width());
int h = (isOnePixel ? 1: brushImage->height());
m_brushGen = brush->gen();
m_brushWidth = w;
m_brushHeight = h;
ImageRef mask;
if (isFloodfill) {
if (isOnePixel) {
mask.reset(Image::create(IMAGE_BITMAP, w, w));
mask->putPixel(0, 0, (color_t)1);
}

View File

@ -63,6 +63,7 @@ void DrawingState::initToolLoop(Editor* editor, MouseMessage* msg)
m_toolLoop->getLayer(),
m_toolLoop->getFrame(),
m_toolLoop->getDstImage(),
m_toolLoop->getCelOrigin(),
(m_toolLoop->getLayer() &&
m_toolLoop->getLayer()->isImage() ?
static_cast<LayerImage*>(m_toolLoop->getLayer())->blendMode():

View File

@ -256,9 +256,10 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
}
}
// Start "moving pixels" loop
if (editor->isInsideSelection() && (msg->left() ||
msg->right())) {
// Start "moving pixels" loop. Here we check only for left-click as
// right-click can be used to deselect/subtract selection, so we
// should drop the selection in this later case.
if (editor->isInsideSelection() && msg->left()) {
// In case that the user is pressing the copy-selection keyboard shortcut.
EditorCustomizationDelegate* customization = editor->getCustomizationDelegate();
if ((customization) &&

View File

@ -182,7 +182,8 @@ void PixelsMovement::cutMask()
m_transaction.execute(new cmd::ClearMask(writer.cel()));
ASSERT(writer.cel());
if (writer.cel())
if (writer.cel() &&
writer.cel()->layer()->isTransparent())
m_transaction.execute(new cmd::TrimCel(writer.cel()));
}
}

View File

@ -98,26 +98,18 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
case WHEEL_FG:
{
int newIndex = 0;
if (ColorBar::instance()->getFgColor().getType() == app::Color::IndexType) {
int lastIndex = get_current_palette()->size()-1;
newIndex = ColorBar::instance()->getFgColor().getIndex() + int(dz);
int newIndex = ColorBar::instance()->getFgColor().getIndex() + int(dz);
newIndex = MID(0, newIndex, lastIndex);
}
ColorBar::instance()->setFgColor(app::Color::fromIndex(newIndex));
}
break;
case WHEEL_BG:
{
int newIndex = 0;
if (ColorBar::instance()->getBgColor().getType() == app::Color::IndexType) {
int lastIndex = get_current_palette()->size()-1;
newIndex = ColorBar::instance()->getBgColor().getIndex() + int(dz);
int newIndex = ColorBar::instance()->getBgColor().getIndex() + int(dz);
newIndex = MID(0, newIndex, lastIndex);
}
ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex));
}
break;

View File

@ -225,7 +225,8 @@ void cut(ContextWriter& writer)
transaction.execute(new cmd::ClearMask(writer.cel()));
ASSERT(writer.cel());
if (writer.cel())
if (writer.cel() &&
writer.cel()->layer()->isTransparent())
transaction.execute(new cmd::TrimCel(writer.cel()));
transaction.execute(new cmd::DeselectMask(writer.document()));

View File

@ -14,6 +14,7 @@
#include "app/app.h"
#include "app/cmd/add_cel.h"
#include "app/cmd/clear_cel.h"
#include "app/cmd/copy_region.h"
#include "app/cmd/patch_cel.h"
#include "app/context.h"
#include "app/document.h"
@ -198,6 +199,15 @@ void ExpandCelCanvas::commit()
regionToPatch = &reduced;
}
if (m_layer->isBackground()) {
m_transaction.execute(
new cmd::CopyRegion(
m_cel->image(),
m_dstImage.get(),
*regionToPatch,
m_bounds.origin()));
}
else {
m_transaction.execute(
new cmd::PatchCel(
m_cel,
@ -205,6 +215,7 @@ void ExpandCelCanvas::commit()
*regionToPatch,
m_bounds.origin()));
}
}
else {
ASSERT(false);
}

View File

@ -70,14 +70,20 @@ doc::Image* render_text(const std::string& fontfile, int fontsize,
}
}
int output_alpha = MUL_UN8(doc::rgba_geta(color), alpha, t);
if (output_alpha) {
doc::color_t output_color =
doc::rgba(doc::rgba_getr(color),
doc::rgba_getg(color),
doc::rgba_getb(color),
output_alpha);
doc::put_pixel(
image, ximg, yimg,
doc::rgba_blender_normal(
doc::get_pixel(image, ximg, yimg),
doc::rgba(doc::rgba_getr(color),
doc::rgba_getg(color),
doc::rgba_getb(color),
MUL_UN8(doc::rgba_geta(color), alpha, t))));
output_color));
}
}
}
});

View File

@ -59,6 +59,11 @@ set(BASE_SOURCES
trim_string.cpp
version.cpp)
if(APPLE)
set(BASE_SOURCES ${BASE_SOURCES}
fs_osx.mm)
endif()
if(WIN32)
set(BASE_SOURCES ${BASE_SOURCES}
win32_exception.cpp)

View File

@ -1,5 +1,5 @@
// Aseprite Base Library
// Copyright (c) 2001-2013, 2015 David Capello
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -36,6 +36,9 @@ namespace base {
std::string get_app_path();
std::string get_temp_path();
std::string get_user_docs_folder();
#if __APPLE__
std::string get_lib_app_support_path();
#endif
// If the given filename is a relative path, it converts the
// filename to an absolute one.

29
src/base/fs_osx.mm Normal file
View File

@ -0,0 +1,29 @@
// Aseprite Base Library
// Copyright (c) 2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <Foundation/Foundation.h>
#include <string>
namespace base {
std::string get_lib_app_support_path()
{
NSArray* dirs = NSSearchPathForDirectoriesInDomains(
NSApplicationSupportDirectory, NSUserDomainMask, YES);
if (dirs) {
NSString* dir = [dirs firstObject];
if (dir)
return std::string([dir UTF8String]);
}
return std::string();
}
} // namespace base

View File

@ -85,6 +85,30 @@ std::string get_file_extension(const std::string& filename)
return result;
}
std::string replace_extension(const std::string& filename, const std::string& extension)
{
std::string::const_reverse_iterator rit;
std::string result;
// search for the first dot from the end of the string
for (rit=filename.rbegin(); rit!=filename.rend(); ++rit) {
if (is_path_separator(*rit))
return result;
else if (*rit == '.')
break;
}
if (rit != filename.rend()) {
std::copy(filename.begin(), std::string::const_iterator(rit.base()),
std::back_inserter(result));
std::copy(extension.begin(), extension.end(),
std::back_inserter(result));
}
return result;
}
std::string get_file_title(const std::string& filename)
{
std::string::const_reverse_iterator rit;

View File

@ -28,6 +28,9 @@ namespace base {
// Returns the extension of the file name (without the dot).
std::string get_file_extension(const std::string& filename);
// Returns the whole path with another extension.
std::string replace_extension(const std::string& filename, const std::string& extension);
// Returns the file name without path and without extension.
std::string get_file_title(const std::string& filename);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -33,7 +33,7 @@ namespace doc {
frame_t frame() const { return m_frame; }
int x() const { return m_data->position().x; }
int y() const { return m_data->position().y; }
gfx::Point position() const { return m_data->position(); }
const gfx::Point& position() const { return m_data->position(); }
int opacity() const { return m_data->opacity(); }
LayerImage* layer() const { return m_layer; }

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -63,6 +63,7 @@ namespace doc {
bool isFolder() const { return type() == ObjectType::LayerFolder; }
bool isBackground() const { return hasFlags(LayerFlags::Background); }
bool isTransparent() const { return !hasFlags(LayerFlags::Background); }
bool isVisible() const { return hasFlags(LayerFlags::Visible); }
bool isEditable() const { return hasFlags(LayerFlags::Editable); }
bool isMovable() const { return !hasFlags(LayerFlags::LockMove); }

View File

@ -78,6 +78,11 @@ Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, co
return trim;
}
Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer)
{
return crop_image(image, bounds.x, bounds.y, bounds.w, bounds.h, bg, buffer);
}
void rotate_image(const Image* src, Image* dst, int angle)
{
ASSERT(src);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2015 David Capello
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -26,6 +26,7 @@ namespace doc {
void copy_image(Image* dst, const Image* src);
void copy_image(Image* dst, const Image* src, int x, int y);
Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
void rotate_image(const Image* src, Image* dst, int angle);
void draw_hline(Image* image, int x1, int y, int x2, color_t c);

View File

@ -484,12 +484,16 @@ void Render::setBgCheckedSize(const gfx::Size& size)
m_bgCheckedSize = size;
}
void Render::setPreviewImage(const Layer* layer, frame_t frame,
Image* image, BlendMode blendMode)
void Render::setPreviewImage(const Layer* layer,
const frame_t frame,
const Image* image,
const gfx::Point& pos,
const BlendMode blendMode)
{
m_selectedLayer = layer;
m_selectedFrame = frame;
m_previewImage = image;
m_previewPos = pos;
m_previewBlendMode = blendMode;
}
@ -657,7 +661,8 @@ void Render::renderSprite(
dstImage,
m_previewImage,
m_sprite->palette(frame),
0, 0,
m_previewPos.x,
m_previewPos.y,
area,
compositeImage,
255,
@ -842,24 +847,27 @@ void Render::renderLayer(
const Cel* cel = layer->cel(frame);
if (cel) {
Palette* pal = m_sprite->palette(frame);
Image* src_image;
const Image* celImage;
gfx::Point celPos;
// Is the 'm_previewImage' set to be used with this layer?
if ((m_previewImage) &&
(m_selectedLayer == layer) &&
(m_selectedFrame == frame)) {
src_image = m_previewImage;
celImage = m_previewImage;
celPos = m_previewPos;
ASSERT(src_image->pixelFormat() == cel->image()->pixelFormat());
ASSERT(celImage->pixelFormat() == cel->image()->pixelFormat());
}
// If not, we use the original cel-image from the images' stock
else {
src_image = cel->image();
celImage = cel->image();
celPos = cel->position();
}
if (src_image) {
if (celImage) {
const LayerImage* imgLayer = static_cast<const LayerImage*>(layer);
BlendMode layerBlendMode =
const BlendMode layerBlendMode =
(blendMode == BlendMode::UNSPECIFIED ?
imgLayer->blendMode():
blendMode);
@ -875,7 +883,7 @@ void Render::renderLayer(
opacity = MUL_UN8(opacity, imgLayer->opacity(), t);
opacity = MUL_UN8(opacity, m_globalOpacity, t);
ASSERT(src_image->maskColor() == m_sprite->transparentColor());
ASSERT(celImage->maskColor() == m_sprite->transparentColor());
// Draw parts outside the "m_extraCel" area
if (drawExtra && m_extraType == ExtraType::PATCH) {
@ -885,8 +893,8 @@ void Render::renderLayer(
for (auto rc : originalAreas) {
renderCel(
image, src_image, pal,
cel, gfx::Clip(area.dst.x+rc.x-area.src.x,
image, celImage, pal, celPos,
gfx::Clip(area.dst.x+rc.x-area.src.x,
area.dst.y+rc.y-area.src.y, rc), compositeImage,
opacity, layerBlendMode);
}
@ -894,8 +902,8 @@ void Render::renderLayer(
// Draw the whole cel
else {
renderCel(
image, src_image, pal,
cel, area, compositeImage,
image, celImage, pal,
celPos, area, compositeImage,
opacity, layerBlendMode);
}
}
@ -927,7 +935,7 @@ void Render::renderLayer(
renderCel(
image, m_extraImage,
m_sprite->palette(frame),
m_extraCel,
m_extraCel->position(),
gfx::Clip(area.dst.x+extraArea.x-area.src.x,
area.dst.y+extraArea.y-area.src.y,
extraArea),
@ -942,7 +950,7 @@ void Render::renderCel(
Image* dst_image,
const Image* cel_image,
const Palette* pal,
const Cel* cel,
const gfx::Point& celPos,
const gfx::Clip& area,
const CompositeImageFunc compositeImage,
const int opacity,
@ -951,8 +959,8 @@ void Render::renderCel(
renderImage(dst_image,
cel_image,
pal,
cel->x(),
cel->y(),
celPos.x,
celPos.y,
area,
compositeImage,
opacity,

View File

@ -13,7 +13,7 @@
#include "doc/color.h"
#include "doc/frame.h"
#include "doc/pixel_format.h"
#include "gfx/fwd.h"
#include "gfx/point.h"
#include "gfx/size.h"
#include "render/extra_type.h"
#include "render/onionskin_position.h"
@ -114,8 +114,11 @@ namespace render {
// Sets the preview image. This preview image is an alternative
// image to be used for the given layer/frame.
void setPreviewImage(const Layer* layer, frame_t frame,
Image* image, BlendMode blendMode);
void setPreviewImage(const Layer* layer,
const frame_t frame,
const Image* image,
const gfx::Point& pos,
const BlendMode blendMode);
void removePreviewImage();
// Sets an extra cel/image to be drawn after the current
@ -191,7 +194,7 @@ namespace render {
Image* dst_image,
const Image* cel_image,
const Palette* pal,
const Cel* cel,
const gfx::Point& celPos,
const gfx::Clip& area,
const CompositeImageFunc compositeImage,
const int opacity,
@ -224,7 +227,8 @@ namespace render {
int m_globalOpacity;
const Layer* m_selectedLayer;
frame_t m_selectedFrame;
Image* m_previewImage;
const Image* m_previewImage;
gfx::Point m_previewPos;
BlendMode m_previewBlendMode;
OnionskinOptions m_onionskin;
};

View File

@ -14,6 +14,8 @@
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)app;
- (void)applicationWillTerminate:(NSNotification*)notification;
- (void)applicationWillResignActive:(NSNotification*)notification;
- (void)applicationDidBecomeActive:(NSNotification*)notification;
- (BOOL)application:(NSApplication*)app openFiles:(NSArray*)filenames;
@end

View File

@ -17,6 +17,7 @@
#include "she/event_queue.h"
#include "she/osx/app.h"
#include "she/osx/generate_drop_files.h"
#include "she/osx/view.h"
#include "she/system.h"
@implementation OSXAppDelegate
@ -38,6 +39,20 @@
she::queue_event(ev);
}
- (void)applicationWillResignActive:(NSNotification*)notification
{
NSEvent* event = [NSApp currentEvent];
if (event != nil)
[OSXView updateKeyFlags:event];
}
- (void)applicationDidBecomeActive:(NSNotification*)notification
{
NSEvent* event = [NSApp currentEvent];
if (event != nil)
[OSXView updateKeyFlags:event];
}
- (BOOL)application:(NSApplication*)app openFiles:(NSArray*)filenames
{
generate_drop_files_from_nsarray(filenames);

View File

@ -28,6 +28,7 @@
- (void)keyDown:(NSEvent*)event;
- (void)keyUp:(NSEvent*)event;
- (void)flagsChanged:(NSEvent*)event;
+ (void)updateKeyFlags:(NSEvent*)event;
- (void)mouseEntered:(NSEvent*)event;
- (void)mouseMoved:(NSEvent*)event;
- (void)mouseExited:(NSEvent*)event;

View File

@ -188,7 +188,11 @@ bool is_key_pressed(KeyScancode scancode)
- (void)flagsChanged:(NSEvent*)event
{
[super flagsChanged:event];
[OSXView updateKeyFlags:event];
}
+ (void)updateKeyFlags:(NSEvent*)event
{
static int lastFlags = 0;
static int flags[] = {
NSShiftKeyMask,

View File

@ -51,20 +51,22 @@ void clear_keyboard_buffer()
extern int app_main(int argc, char* argv[]);
#if _WIN32
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
int argc = 0;
LPWSTR* argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
extern int __argc;
extern wchar_t** __wargv;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
int argc = __argc;
char** argv;
if (argvW && argc > 0) {
if (__wargv && argc > 0) {
argv = new char*[argc];
for (int i=0; i<argc; ++i)
argv[i] = base_strdup(base::to_utf8(std::wstring(argvW[i])).c_str());
LocalFree(argvW);
argv[i] = base_strdup(base::to_utf8(std::wstring(__wargv[i])).c_str());
}
else {
argv = new char*[1];
argv[0] = base_strdup("");
argc = 1;
}
#else
int main(int argc, char* argv[]) {