mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-08 22:13:23 +00:00
Merge branch 'master' into beta
This commit is contained in:
commit
36ffc8d2bd
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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,24 +74,18 @@ void ChangeColorCommand::onExecute(Context* context)
|
||||
case None:
|
||||
// do nothing
|
||||
break;
|
||||
case IncrementIndex:
|
||||
if (color.getType() == app::Color::IndexType) {
|
||||
int index = color.getIndex();
|
||||
if (index < get_current_palette()->size()-1)
|
||||
color = app::Color::fromIndex(index+1);
|
||||
}
|
||||
else
|
||||
color = app::Color::fromIndex(0);
|
||||
case IncrementIndex: {
|
||||
int index = color.getIndex();
|
||||
if (index < get_current_palette()->size()-1)
|
||||
color = app::Color::fromIndex(index+1);
|
||||
break;
|
||||
case DecrementIndex:
|
||||
if (color.getType() == app::Color::IndexType) {
|
||||
int index = color.getIndex();
|
||||
if (index > 0)
|
||||
color = app::Color::fromIndex(index-1);
|
||||
}
|
||||
else
|
||||
color = app::Color::fromIndex(0);
|
||||
}
|
||||
case DecrementIndex: {
|
||||
int index = color.getIndex();
|
||||
if (index > 0)
|
||||
color = app::Color::fromIndex(index-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_background)
|
||||
|
@ -117,7 +117,8 @@ void FlipCommand::onExecute(Context* context)
|
||||
else
|
||||
api.flipImage(image, flipBounds, m_flipType);
|
||||
|
||||
transaction.execute(new cmd::TrimCel(cel));
|
||||
if (cel->layer()->isTransparent())
|
||||
transaction.execute(new cmd::TrimCel(cel));
|
||||
}
|
||||
// When the mask is bigger than the cel bounds, we have to
|
||||
// expand the cel, make the flip, and shrink it again.
|
||||
|
@ -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 (...) {
|
||||
|
@ -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_changeButton.reset();
|
||||
m_deleteButton.reset();
|
||||
m_addButton.reset();
|
||||
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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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());
|
||||
|
526
src/app/file/pixly_format.cpp
Normal file
526
src/app/file/pixly_format.cpp
Normal 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
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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) { }
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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 &&
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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():
|
||||
|
@ -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) &&
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
newIndex = MID(0, newIndex, lastIndex);
|
||||
}
|
||||
int lastIndex = get_current_palette()->size()-1;
|
||||
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);
|
||||
newIndex = MID(0, newIndex, lastIndex);
|
||||
}
|
||||
int lastIndex = get_current_palette()->size()-1;
|
||||
int newIndex = ColorBar::instance()->getBgColor().getIndex() + int(dz);
|
||||
newIndex = MID(0, newIndex, lastIndex);
|
||||
ColorBar::instance()->setBgColor(app::Color::fromIndex(newIndex));
|
||||
}
|
||||
break;
|
||||
|
@ -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()));
|
||||
|
@ -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,12 +199,22 @@ void ExpandCelCanvas::commit()
|
||||
regionToPatch = &reduced;
|
||||
}
|
||||
|
||||
m_transaction.execute(
|
||||
new cmd::PatchCel(
|
||||
m_cel,
|
||||
m_dstImage.get(),
|
||||
*regionToPatch,
|
||||
m_bounds.origin()));
|
||||
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,
|
||||
m_dstImage.get(),
|
||||
*regionToPatch,
|
||||
m_bounds.origin()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
ASSERT(false);
|
||||
|
@ -70,14 +70,20 @@ doc::Image* render_text(const std::string& fontfile, int fontsize,
|
||||
}
|
||||
}
|
||||
|
||||
doc::put_pixel(
|
||||
image, ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image, ximg, yimg),
|
||||
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),
|
||||
MUL_UN8(doc::rgba_geta(color), alpha, t))));
|
||||
output_alpha);
|
||||
|
||||
doc::put_pixel(
|
||||
image, ximg, yimg,
|
||||
doc::rgba_blender_normal(
|
||||
doc::get_pixel(image, ximg, yimg),
|
||||
output_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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
29
src/base/fs_osx.mm
Normal 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
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -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.
|
||||
@ -62,11 +62,12 @@ namespace doc {
|
||||
bool isImage() const { return type() == ObjectType::LayerImage; }
|
||||
bool isFolder() const { return type() == ObjectType::LayerFolder; }
|
||||
|
||||
bool isBackground() 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); }
|
||||
bool isContinuous() const { return hasFlags(LayerFlags::Continuous); }
|
||||
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); }
|
||||
bool isContinuous() const { return hasFlags(LayerFlags::Continuous); }
|
||||
|
||||
void setBackground(bool state) { switchFlags(LayerFlags::Background, state); }
|
||||
void setVisible (bool state) { switchFlags(LayerFlags::Visible, state); }
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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,17 +893,17 @@ void Render::renderLayer(
|
||||
|
||||
for (auto rc : originalAreas) {
|
||||
renderCel(
|
||||
image, src_image, pal,
|
||||
cel, gfx::Clip(area.dst.x+rc.x-area.src.x,
|
||||
area.dst.y+rc.y-area.src.y, rc), compositeImage,
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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,
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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[]) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user