Unify all render code in one library

Changes:
* Create render library (move util/render.cpp to render/render.cpp)
* Move app::Zoom class to render::Zoom
* Remove doc::Image::merge() member function
* Add gfx::Clip helper class (to clip dst/src rectangles before a blit)
* Move doc::composite_image() to render::composite_image()
* Remove doc::Sprite::render()
* Replace Sprite::getPixel() with render::get_sprite_pixel()
* Remove doc::layer_render() function
* Convert DitheringMethod to a enum class
* Add AppRender to configure a render::Render with the app configuration
* Move checked background preferences as document-specific configuration
* Add doc::Sprite::layer() and palette() member functions
* Add doc::Layer::cel() member function
* Add doc::Palette::entry() member function()
* Add doc::frame_t type
* Move create_palette_from_rgb/convert_pixel_format to render library
* ExportSpriteSheet doesn't need a temporary image now that we can specify
  the source rectangle in the render routine
This commit is contained in:
David Capello 2014-12-28 11:06:11 -03:00
parent 73658399cc
commit da1358c5dc
94 changed files with 2204 additions and 1496 deletions

11
TODO.md
View File

@ -43,8 +43,17 @@
# Refactoring
* Convert doc::PixelFormat to a enum class
* Add doc::Spec with width/height/channels/ColorMode/ncolors
* Convert doc::LayerIndex -> typedef int doc::layer_t;
* Convert doc::FrameNumber -> typedef int doc::frame_t;
* Replace doc::LayerImage::getCel() with doc::Layer::cel()
* Replace doc::Sprite::getPalette() with doc::Sprite::palette()
* Replace doc::Palette::getEntry() with doc::Palette::entry()
* Remove LayerFolder, replace it with an array of layers
* Add new "level" into Layer class
* Refactor src/file/ in several layers.
* Use streams instead of FILEs.
* Use streams instead of FILEs and create load/save tests with streams.
* Destroy modules/gui.h.
* Convert update_screen_for_document in an event from contexts or
something similar.

View File

@ -46,6 +46,12 @@
<value id="FAST" value="0" />
<value id="ROTSPRITE" value="1" />
</enum>
<enum id="BgType">
<value id="CHECKED_16x16" value="0" />
<value id="CHECKED_8x8" value="1" />
<value id="CHECKED_4x4" value="2" />
<value id="CHECKED_2x2" value="3" />
</enum>
</types>
<global>
@ -116,6 +122,12 @@
<option id="opacity" type="int" default="160" />
<option id="auto_opacity" type="bool" default="true" />
</section>
<section id="bg">
<option id="type" type="BgType" default="BgType::CHECKED_16x16" migrate="Option.CheckedBgType" />
<option id="zoom" type="bool" default="true" migrate="Option.CheckedBgZoom" />
<option id="color1" type="app::Color" default="app::Color::fromRgb(128, 128, 128)" migrate="Option.CheckedBgColor1" />
<option id="color2" type="app::Color" default="app::Color::fromRgb(192, 192, 192)" migrate="Option.CheckedBgColor2" />
</section>
<section id="onionskin">
<option id="active" type="bool" default="false" />
<option id="prev_frames" type="int" default="1" />

View File

@ -28,6 +28,7 @@ set(aseprite_libraries
cfg-lib
css-lib
doc-lib
render-lib
scripting-lib
undo-lib
filters-lib
@ -210,6 +211,7 @@ add_subdirectory(base)
add_subdirectory(cfg)
add_subdirectory(css)
add_subdirectory(doc)
add_subdirectory(render)
add_subdirectory(filters)
add_subdirectory(fixmath)
add_subdirectory(gen)
@ -328,6 +330,7 @@ endfunction()
find_tests(base base-lib ${sys_libs})
find_tests(gfx gfx-lib base-lib ${libs3rdparty} ${sys_libs})
find_tests(doc doc-lib gfx-lib base-lib ${libs3rdparty} ${sys_libs})
find_tests(render render-lib doc-lib gfx-lib base-lib ${libs3rdparty} ${sys_libs})
find_tests(css css-lib gfx-lib base-lib ${libs3rdparty} ${sys_libs})
find_tests(ui ui-lib she gfx-lib base-lib ${libs3rdparty} ${sys_libs})
find_tests(app/file ${all_libs})

View File

@ -32,16 +32,17 @@ because they don't depend on any other component.
## Level 2
* [filters](filters/) (base, doc, gfx): Effects for images.
* [render](render/) (base, gfx, doc): Library to render documents.
* [ui](ui/) (base, gfx, she): Portable UI library (buttons, windows, text fields, etc.)
* [updater](updater/) (base, net): Component to check for updates.
## Level 3
* [iff](iff/) (base, doc): Image File Formats library (load/save documents).
* [iff](iff/) (base, doc, render): Image File Formats library (load/save documents).
## Level 4
* [app](app/) (allegro, base, doc, filters, gfx, iff, scripting, she, ui, undo, updater, webserver)
* [app](app/) (allegro, base, doc, filters, gfx, iff, render, scripting, she, ui, undo, updater, webserver)
## Level 5

View File

@ -50,6 +50,7 @@ add_library(app-lib
app.cpp
app_menus.cpp
app_options.cpp
app_render.cpp
backup.cpp
check_update.cpp
color.cpp
@ -303,10 +304,8 @@ add_library(app-lib
util/msk_file.cpp
util/pic_file.cpp
util/range_utils.cpp
util/render.cpp
webserver.cpp
widget_loader.cpp
xml_document.cpp
xml_exception.cpp
zoom.cpp
${generated_files})

View File

@ -64,7 +64,6 @@
#include "app/ui/toolbar.h"
#include "app/ui_context.h"
#include "app/util/boundary.h"
#include "app/util/render.h"
#include "app/webserver.h"
#include "base/exception.h"
#include "base/fs.h"
@ -75,6 +74,7 @@
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "render/render.h"
#include "scripting/engine.h"
#include "she/display.h"
#include "she/error.h"
@ -153,8 +153,6 @@ void App::initialize(const AppOptions& options)
// init editor cursor
Editor::editor_cursor_init();
// Load RenderEngine configuration
RenderEngine::loadConfig();
if (isPortable())
PRINTF("Running in portable mode\n");
@ -492,6 +490,11 @@ App::~App()
m_instance = NULL;
}
catch (const std::exception& e) {
she::error_message(e.what());
// no re-throw
}
catch (...) {
she::error_message("Error closing ASE.\n(uncaught exception)");

76
src/app/app_render.cpp Normal file
View File

@ -0,0 +1,76 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/app_render.h"
#include "app/app.h"
#include "app/color_utils.h"
#include "app/pref/preferences.h"
#include "render/render.h"
namespace app {
AppRender::AppRender()
{
}
AppRender::AppRender(app::Document* doc, doc::PixelFormat pixelFormat)
{
setupBackground(doc, pixelFormat);
}
void AppRender::setupBackground(app::Document* doc, doc::PixelFormat pixelFormat)
{
DocumentPreferences& docPref = App::instance()->preferences().document(doc);
render::BgType bgType;
gfx::Size tile;
switch (docPref.bg.type()) {
case app::gen::BgType::CHECKED_16x16:
bgType = render::BgType::CHECKED;
tile = gfx::Size(16, 16);
break;
case app::gen::BgType::CHECKED_8x8:
bgType = render::BgType::CHECKED;
tile = gfx::Size(8, 8);
break;
case app::gen::BgType::CHECKED_4x4:
bgType = render::BgType::CHECKED;
tile = gfx::Size(4, 4);
break;
case app::gen::BgType::CHECKED_2x2:
bgType = render::BgType::CHECKED;
tile = gfx::Size(2, 2);
break;
default:
bgType = render::BgType::TRANSPARENT;
break;
}
setBgType(bgType);
setBgZoom(docPref.bg.zoom());
setBgColor1(color_utils::color_for_image(docPref.bg.color1(), pixelFormat));
setBgColor2(color_utils::color_for_image(docPref.bg.color2(), pixelFormat));
setBgCheckedSize(tile);
}
}

View File

@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
* Copyright (C) 2001-2013 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,32 +16,24 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifndef APP_RENDER_H_INCLUDED
#define APP_RENDER_H_INCLUDED
#pragma once
#include "app/zoom.h"
#include "doc/pixel_format.h"
#include "render/render.h"
namespace app {
class Document;
void Zoom::in()
{
if (m_den > 1) {
m_den--;
}
else if (m_num < 64) {
m_num++;
}
}
class AppRender : public render::Render {
public:
AppRender();
AppRender(app::Document* doc, doc::PixelFormat pixelFormat);
void Zoom::out()
{
if (m_num > 1) {
m_num--;
}
else if (m_den < 32) {
m_den++;
}
}
void setupBackground(app::Document* doc, doc::PixelFormat pixelFormat);
};
} // namespace app
#endif // APP_RENDER_H_INCLUDED

View File

@ -28,6 +28,7 @@
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "gfx/point.h"
#include "render/get_sprite_pixel.h"
namespace app {
@ -47,7 +48,7 @@ void ColorPicker::pickColor(const DocumentLocation& location,
if (mode == FromComposition) { // Pick from the composed image
m_color = app::Color::fromImage(
location.sprite()->pixelFormat(),
location.sprite()->getPixel(pos.x, pos.y, location.frame()));
render::get_sprite_pixel(location.sprite(), pos.x, pos.y, location.frame()));
doc::CelList cels;
location.sprite()->pickCels(pos.x, pos.y, location.frame(), 128, cels);

View File

@ -54,7 +54,7 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
CmdUIOnlyFlag)
{
m_format = IMAGE_RGB;
m_dithering = DITHERING_NONE;
m_dithering = DitheringMethod::NONE;
}
void ChangePixelFormatCommand::onLoadParams(Params* params)
@ -66,9 +66,9 @@ void ChangePixelFormatCommand::onLoadParams(Params* params)
std::string dithering = params->get("dithering");
if (dithering == "ordered")
m_dithering = DITHERING_ORDERED;
m_dithering = DitheringMethod::ORDERED;
else
m_dithering = DITHERING_NONE;
m_dithering = DitheringMethod::NONE;
}
bool ChangePixelFormatCommand::onEnabled(Context* context)
@ -79,7 +79,7 @@ bool ChangePixelFormatCommand::onEnabled(Context* context)
if (sprite != NULL &&
sprite->pixelFormat() == IMAGE_INDEXED &&
m_format == IMAGE_INDEXED &&
m_dithering == DITHERING_ORDERED)
m_dithering == DitheringMethod::ORDERED)
return false;
return sprite != NULL;
@ -93,7 +93,7 @@ bool ChangePixelFormatCommand::onChecked(Context* context)
if (sprite != NULL &&
sprite->pixelFormat() == IMAGE_INDEXED &&
m_format == IMAGE_INDEXED &&
m_dithering == DITHERING_ORDERED)
m_dithering == DitheringMethod::ORDERED)
return false;
return

View File

@ -328,17 +328,15 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
columns = sheet_w / sprite->width();
base::UniquePtr<Image> resultImage(Image::create(sprite->pixelFormat(), sheet_w, sheet_h));
base::UniquePtr<Image> tempImage(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height()));
doc::clear_image(resultImage, 0);
render::Render render;
int column = 0, row = 0;
for (FrameNumber frame(0); frame<nframes; ++frame) {
// TODO "tempImage" could not be necessary if we could specify
// destination clipping bounds in Sprite::render() function.
tempImage->clear(0);
sprite->render(tempImage, 0, 0, frame);
resultImage->copy(tempImage, column*sprite->width(), row*sprite->height(),
0, 0, tempImage->width(), tempImage->height());
render.renderSprite(resultImage, sprite, frame,
gfx::Clip(column*sprite->width(), row*sprite->height(),
sprite->bounds()));
if (++column >= columns) {
column = 0;

View File

@ -165,17 +165,18 @@ protected:
try {
Sprite* sprite = m_document->sprite();
FrameNumber currentFrame = m_context->activeLocation().frame();
render::Render render;
// As first step, we cut each tile and add them into "animation" list.
for (int y=m_rect.y; y<sprite->height(); y += m_rect.h) {
for (int x=m_rect.x; x<sprite->width(); x += m_rect.w) {
base::UniquePtr<Image> resultImage(Image::create(sprite->pixelFormat(), m_rect.w, m_rect.h));
// Clear the image with mask color.
doc::clear_image(resultImage, 0);
base::UniquePtr<Image> resultImage(
Image::create(sprite->pixelFormat(), m_rect.w, m_rect.h));
// Render the portion of sheet.
sprite->render(resultImage, -x, -y, currentFrame);
render.renderSprite(resultImage, sprite, currentFrame,
gfx::Clip(0, 0, x, y, m_rect.w, m_rect.h));
animation.push_back(resultImage);
resultImage.release();
}

View File

@ -98,9 +98,9 @@ void InvertMaskCommand::onExecute(Context* context)
if (document->mask()->bitmap()) {
// Copy the inverted region in the new mask
doc::copy_image(mask->bitmap(),
document->mask()->bitmap(),
document->mask()->bounds().x,
document->mask()->bounds().y);
document->mask()->bitmap(),
document->mask()->bounds().x,
document->mask()->bounds().y);
}
// We need only need the area inside the sprite

View File

@ -39,6 +39,7 @@
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "doc/stock.h"
#include "render/render.h"
#include "ui/ui.h"
namespace app {
@ -158,11 +159,11 @@ void MergeDownLayerCommand::onExecute(Context* context)
x2-x1+1, y2-y1+1, bgcolor);
// Merge src_image in new_image
doc::composite_image(new_image, src_image,
src_cel->x()-x1,
src_cel->y()-y1,
src_cel->opacity(),
static_cast<LayerImage*>(src_layer)->getBlendMode());
render::composite_image(new_image, src_image,
src_cel->x()-x1,
src_cel->y()-y1,
src_cel->opacity(),
static_cast<LayerImage*>(src_layer)->getBlendMode());
if (undo.isEnabled())
undo.pushUndoer(new undoers::SetCelPosition(undo.getObjects(), dst_cel));

View File

@ -36,10 +36,10 @@
#include "app/settings/settings.h"
#include "app/ui/color_button.h"
#include "app/ui/editor/editor.h"
#include "app/util/render.h"
#include "base/bind.h"
#include "base/path.h"
#include "doc/image.h"
#include "render/render.h"
#include "she/system.h"
#include "ui/ui.h"
@ -54,14 +54,14 @@ public:
OptionsWindow(Context* context)
: m_settings(context->settings())
, m_docSettings(m_settings->getDocumentSettings(context->activeDocument()))
, m_checked_bg_color1(new ColorButton(RenderEngine::getCheckedBgColor1(), IMAGE_RGB))
, m_checked_bg_color2(new ColorButton(RenderEngine::getCheckedBgColor2(), IMAGE_RGB))
, m_preferences(App::instance()->preferences())
, m_docPref(m_preferences.document(context->activeDocument()))
, m_checked_bg_color1(new ColorButton(m_docPref.bg.color1(), IMAGE_RGB))
, m_checked_bg_color2(new ColorButton(m_docPref.bg.color2(), IMAGE_RGB))
, m_pixelGridColor(new ColorButton(m_docSettings->getPixelGridColor(), IMAGE_RGB))
, m_gridColor(new ColorButton(m_docSettings->getGridColor(), IMAGE_RGB))
, m_cursorColor(new ColorButton(Editor::get_cursor_color(), IMAGE_RGB))
{
Preferences& preferences = App::instance()->preferences();
sectionListbox()->ChangeSelectedItem.connect(Bind<void>(&OptionsWindow::onChangeSection, this));
cursorColorBox()->addChild(m_cursorColor);
@ -78,10 +78,10 @@ public:
pixelGridAutoOpacity()->setSelected(m_docSettings->getPixelGridAutoOpacity());
// Others
if (preferences.general.autoshowTimeline())
if (m_preferences.general.autoshowTimeline())
autotimeline()->setSelected(true);
if (preferences.general.expandMenubarOnMouseover())
if (m_preferences.general.expandMenubarOnMouseover())
expandMenubarOnMouseover()->setSelected(true);
if (m_settings->getCenterOnZoom())
@ -117,10 +117,10 @@ public:
checkedBgSize()->addItem("8x8");
checkedBgSize()->addItem("4x4");
checkedBgSize()->addItem("2x2");
checkedBgSize()->setSelectedItemIndex((int)RenderEngine::getCheckedBgType());
checkedBgSize()->setSelectedItemIndex(int(m_docPref.bg.type()));
// Zoom checked background
if (RenderEngine::getCheckedBgZoom())
if (m_docPref.bg.zoom())
checkedBgZoom()->setSelected(true);
// Checked background colors
@ -153,8 +153,6 @@ public:
}
void saveConfig() {
Preferences& preferences = App::instance()->preferences();
Editor::set_cursor_color(m_cursorColor->getColor());
m_docSettings->setGridColor(m_gridColor->getColor());
m_docSettings->setGridOpacity(gridOpacity()->getValue());
@ -163,10 +161,10 @@ public:
m_docSettings->setPixelGridOpacity(pixelGridOpacity()->getValue());
m_docSettings->setPixelGridAutoOpacity(pixelGridAutoOpacity()->isSelected());
preferences.general.autoshowTimeline(autotimeline()->isSelected());
m_preferences.general.autoshowTimeline(autotimeline()->isSelected());
bool expandOnMouseover = expandMenubarOnMouseover()->isSelected();
preferences.general.expandMenubarOnMouseover(expandOnMouseover);
m_preferences.general.expandMenubarOnMouseover(expandOnMouseover);
ui::MenuBar::setExpandOnMouseover(expandOnMouseover);
m_settings->setCenterOnZoom(centerOnZoom()->isSelected());
@ -175,10 +173,10 @@ public:
m_settings->setZoomWithScrollWheel(wheelZoom()->isSelected());
m_settings->setRightClickMode(static_cast<RightClickMode>(rightClickBehavior()->getSelectedItemIndex()));
RenderEngine::setCheckedBgType((RenderEngine::CheckedBgType)checkedBgSize()->getSelectedItemIndex());
RenderEngine::setCheckedBgZoom(checkedBgZoom()->isSelected());
RenderEngine::setCheckedBgColor1(m_checked_bg_color1->getColor());
RenderEngine::setCheckedBgColor2(m_checked_bg_color2->getColor());
m_docPref.bg.type(app::gen::BgType(checkedBgSize()->getSelectedItemIndex()));
m_docPref.bg.zoom(checkedBgZoom()->isSelected());
m_docPref.bg.color1(m_checked_bg_color1->getColor());
m_docPref.bg.color2(m_checked_bg_color2->getColor());
int undo_size_limit_value;
undo_size_limit_value = undoSizeLimit()->getTextInt();
@ -226,10 +224,10 @@ private:
pixelGridOpacity()->setValue(200);
pixelGridAutoOpacity()->setSelected(true);
checkedBgSize()->setSelectedItemIndex((int)RenderEngine::CHECKED_BG_16X16);
checkedBgZoom()->setSelected(true);
m_checked_bg_color1->setColor(app::Color::fromRgb(128, 128, 128));
m_checked_bg_color2->setColor(app::Color::fromRgb(192, 192, 192));
checkedBgSize()->setSelectedItemIndex(int(m_docPref.bg.type.defaultValue()));
checkedBgZoom()->setSelected(m_docPref.bg.zoom.defaultValue());
m_checked_bg_color1->setColor(m_docPref.bg.color1.defaultValue());
m_checked_bg_color2->setColor(m_docPref.bg.color2.defaultValue());
}
void onLocateCrashFolder() {
@ -242,6 +240,8 @@ private:
ISettings* m_settings;
IDocumentSettings* m_docSettings;
Preferences& m_preferences;
DocumentPreferences& m_docPref;
ColorButton* m_checked_bg_color1;
ColorButton* m_checked_bg_color2;
ColorButton* m_pixelGridColor;

View File

@ -49,14 +49,14 @@
#include "base/bind.h"
#include "base/fs.h"
#include "base/path.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "doc/stock.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "gfx/size.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/quantization.h"
#include "doc/sprite.h"
#include "doc/stock.h"
#include "render/quantization.h"
#include "ui/graphics.h"
#include "ui/ui.h"
@ -587,7 +587,7 @@ void PaletteEntryEditor::onQuantizeClick(Event& ev)
return;
}
palette = quantization::create_palette_from_rgb(
palette = render::create_palette_from_rgb(
sprite, reader.frame(), NULL);
}

View File

@ -23,6 +23,7 @@
#include "ui/ui.h"
#include "app/app.h"
#include "app/app_render.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/context.h"
@ -33,7 +34,6 @@
#include "app/ui/editor/editor.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/status_bar.h"
#include "app/util/render.h"
#include "doc/conversion_she.h"
#include "doc/image.h"
#include "doc/palette.h"
@ -182,18 +182,18 @@ protected:
virtual void onPaint(PaintEvent& ev) override {
Graphics* g = ev.getGraphics();
AppRender& render = Editor::renderEngine();
render.disableOnionskin();
render.setBgType(render::BgType::TRANSPARENT);
// Render sprite and leave the result in 'render' variable
// Render sprite and leave the result in 'm_render' variable
if (m_render == NULL) {
RenderEngine renderEngine(
m_doc, m_sprite,
m_editor->layer(),
m_editor->frame());
ImageBufferPtr buf = Editor::getRenderImageBuffer();
m_render.reset(
renderEngine.renderSprite(m_sprite->bounds(),
m_editor->frame(), Zoom(1, 1), false, false, buf));
m_render.reset(Image::create(IMAGE_RGB,
m_sprite->width(), m_sprite->height(), buf));
render.renderSprite(
m_render.get(), m_sprite, m_editor->frame());
}
int x, y, w, h, u, v;
@ -205,27 +205,36 @@ protected:
if (m_tiled & TILED_X_AXIS) x = SGN(x) * (ABS(x)%w);
if (m_tiled & TILED_Y_AXIS) y = SGN(y) * (ABS(y)%h);
if (m_index_bg_color == -1)
RenderEngine::renderCheckedBackground(m_doublebuf, -m_pos.x, -m_pos.y, m_zoom);
else
if (m_index_bg_color == -1) {
render.setupBackground(m_doc, m_doublebuf->pixelFormat());
render.renderBackground(m_doublebuf,
gfx::Clip(0, 0, -m_pos.x, -m_pos.y,
m_doublebuf->width(), m_doublebuf->height()), m_zoom);
}
else {
doc::clear_image(m_doublebuf, m_pal->getEntry(m_index_bg_color));
}
switch (m_tiled) {
case TILED_NONE:
RenderEngine::renderImage(m_doublebuf, m_render, m_pal, x, y, m_zoom);
render.renderImage(m_doublebuf, m_render, m_pal, x, y,
m_zoom, 255, BLEND_MODE_NORMAL);
break;
case TILED_X_AXIS:
for (u=x-w; u<ui::display_w()+w; u+=w)
RenderEngine::renderImage(m_doublebuf, m_render, m_pal, u, y, m_zoom);
render.renderImage(m_doublebuf, m_render, m_pal, u, y,
m_zoom, 255, BLEND_MODE_NORMAL);
break;
case TILED_Y_AXIS:
for (v=y-h; v<ui::display_h()+h; v+=h)
RenderEngine::renderImage(m_doublebuf, m_render, m_pal, x, v, m_zoom);
render.renderImage(m_doublebuf, m_render, m_pal, x, v,
m_zoom, 255, BLEND_MODE_NORMAL);
break;
case TILED_BOTH:
for (v=y-h; v<ui::display_h()+h; v+=h)
for (u=x-w; u<ui::display_w()+w; u+=w)
RenderEngine::renderImage(m_doublebuf, m_render, m_pal, u, v, m_zoom);
render.renderImage(m_doublebuf, m_render, m_pal, u, v,
m_zoom, 255, BLEND_MODE_NORMAL);
break;
}
@ -243,7 +252,7 @@ private:
gfx::Point m_pos;
gfx::Point m_oldMousePos;
gfx::Point m_delta;
Zoom m_zoom;
render::Zoom m_zoom;
int m_index_bg_color;
base::UniquePtr<Image> m_render;
base::UniquePtr<Image> m_doublebuf;

View File

@ -25,6 +25,7 @@
#include "app/modules/editors.h"
#include "app/ui/editor/editor.h"
#include "base/convert_to.h"
#include "render/zoom.h"
namespace app {
@ -74,7 +75,7 @@ bool ZoomCommand::onEnabled(Context* context)
void ZoomCommand::onExecute(Context* context)
{
Zoom zoom = current_editor->zoom();
render::Zoom zoom = current_editor->zoom();
switch (m_action) {
case In:
@ -85,12 +86,12 @@ void ZoomCommand::onExecute(Context* context)
break;
case Set:
switch (m_percentage) {
case 3200: zoom = Zoom(32, 1); break;
case 1600: zoom = Zoom(16, 1); break;
case 800: zoom = Zoom(8, 1); break;
case 400: zoom = Zoom(4, 1); break;
case 200: zoom = Zoom(2, 1); break;
default: zoom = Zoom(1, 1); break;
case 3200: zoom = render::Zoom(32, 1); break;
case 1600: zoom = render::Zoom(16, 1); break;
case 800: zoom = render::Zoom(8, 1); break;
case 400: zoom = render::Zoom(4, 1); break;
case 200: zoom = render::Zoom(2, 1); break;
default: zoom = render::Zoom(1, 1); break;
}
break;
}

View File

@ -155,30 +155,31 @@ void FilterManagerImpl::end()
bool FilterManagerImpl::applyStep()
{
if ((m_row >= 0) && (m_row < m_h)) {
if ((m_mask) && (m_mask->bitmap())) {
int x = m_x - m_mask->bounds().x + m_offset_x;
int y = m_row + m_y - m_mask->bounds().y + m_offset_y;
m_maskBits = m_mask->bitmap()
->lockBits<BitmapTraits>(Image::ReadLock,
gfx::Rect(x, y, m_w - x, m_h - y));
m_maskIterator = m_maskBits.begin();
}
switch (m_location.sprite()->pixelFormat()) {
case IMAGE_RGB: m_filter->applyToRgba(this); break;
case IMAGE_GRAYSCALE: m_filter->applyToGrayscale(this); break;
case IMAGE_INDEXED: m_filter->applyToIndexed(this); break;
}
++m_row;
return true;
}
else {
if (m_row < 0 || m_row >= m_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))
return false;
m_maskBits = m_mask->bitmap()
->lockBits<BitmapTraits>(Image::ReadLock,
gfx::Rect(x, y, m_w - x, m_h - y));
m_maskIterator = m_maskBits.begin();
}
switch (m_location.sprite()->pixelFormat()) {
case IMAGE_RGB: m_filter->applyToRgba(this); break;
case IMAGE_GRAYSCALE: m_filter->applyToGrayscale(this); break;
case IMAGE_INDEXED: m_filter->applyToIndexed(this); break;
}
++m_row;
return true;
}
void FilterManagerImpl::apply()
@ -204,7 +205,7 @@ void FilterManagerImpl::apply()
undo.pushUndoer(new undoers::ImageArea(undo.getObjects(), m_src, m_x, m_y, m_w, m_h));
// Copy "dst" to "src"
copy_image(m_src, m_dst, 0, 0);
copy_image(m_src, m_dst);
undo.commit();
}

View File

@ -23,11 +23,11 @@
#include "app/commands/filters/filter_preview.h"
#include "app/commands/filters/filter_manager_impl.h"
#include "app/ui/editor/editor.h"
#include "doc/sprite.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/widget.h"
#include "app/util/render.h"
namespace app {
@ -75,14 +75,14 @@ bool FilterPreview::onProcessMessage(Message* msg)
switch (msg->type()) {
case kOpenMessage:
RenderEngine::setPreviewImage(
Editor::renderEngine().setPreviewImage(
m_filterMgr->layer(),
m_filterMgr->frame(),
m_filterMgr->destinationImage());
break;
case kCloseMessage:
RenderEngine::setPreviewImage(NULL, FrameNumber(0), NULL);
Editor::renderEngine().removePreviewImage();
// Stop the preview timer.
m_timer.stop();

View File

@ -59,22 +59,23 @@
#include "app/undoers/set_sprite_transparent_color.h"
#include "app/undoers/set_total_frames.h"
#include "base/unique_ptr.h"
#include "doc/context.h"
#include "doc/document_event.h"
#include "doc/document_observer.h"
#include "doc/algorithm/flip_image.h"
#include "doc/algorithm/shrink_bounds.h"
#include "doc/blend.h"
#include "doc/cel.h"
#include "doc/context.h"
#include "doc/dirty.h"
#include "doc/document_event.h"
#include "doc/document_observer.h"
#include "doc/image.h"
#include "doc/image_bits.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/palette.h"
#include "doc/quantization.h"
#include "doc/sprite.h"
#include "doc/stock.h"
#include "render/quantization.h"
#include "render/render.h"
namespace app {
@ -138,11 +139,10 @@ void DocumentApi::trimSprite(Sprite* sprite)
sprite->width(),
sprite->height()));
Image* image = image_wrap.get();
render::Render render;
for (FrameNumber frame(0); frame<sprite->totalFrames(); ++frame) {
image->clear(0);
sprite->render(image, 0, 0, frame);
render.renderSprite(image, sprite, frame);
// TODO configurable (what color pixel to use as "refpixel",
// here we are using the top-left pixel by default)
@ -190,7 +190,7 @@ void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, Ditherin
}
}
new_image = quantization::convert_pixel_format
new_image = render::convert_pixel_format
(old_image, NULL, newFormat, dithering_method, rgbmap,
sprite->getPalette(frame),
is_image_from_background);
@ -726,7 +726,7 @@ void DocumentApi::moveCel(
int blend = (srcLayer->isBackground() ?
BLEND_MODE_COPY: BLEND_MODE_NORMAL);
composite_image(dstImage, srcImage,
render::composite_image(dstImage, srcImage,
srcCel->x(), srcCel->y(), 255, blend);
}
@ -748,7 +748,7 @@ void DocumentApi::moveCel(
}
if (dstLayer->isBackground()) {
composite_image(dstImage, srcImage,
render::composite_image(dstImage, srcImage,
srcCel->x(), srcCel->y(), 255, BLEND_MODE_NORMAL);
}
else {
@ -792,7 +792,7 @@ void DocumentApi::copyCel(
int blend = (srcLayer->isBackground() ?
BLEND_MODE_COPY: BLEND_MODE_NORMAL);
composite_image(dstImage, srcImage,
render::composite_image(dstImage, srcImage,
srcCel->x(), srcCel->y(), 255, blend);
}
}
@ -996,7 +996,7 @@ void DocumentApi::backgroundFromLayer(LayerImage* layer)
ASSERT(cel_image);
clear_image(bg_image, bgcolor);
composite_image(bg_image, cel_image,
render::composite_image(bg_image, cel_image,
cel->x(), cel->y(),
MID(0, cel->opacity(), 255),
layer->getBlendMode());
@ -1078,13 +1078,15 @@ void DocumentApi::flattenLayers(Sprite* sprite)
configureLayerAsBackground(background);
}
render::Render render;
render.setBgType(render::BgType::NONE);
color_t bgcolor = bgColor(background);
// Copy all frames to the background.
for (FrameNumber frame(0); frame<sprite->totalFrames(); ++frame) {
// Clear the image and render this frame.
clear_image(image, bgcolor);
layer_render(sprite->folder(), image, 0, 0, frame);
render.renderSprite(image, sprite, frame);
cel = background->getCel(frame);
if (cel) {
@ -1304,17 +1306,6 @@ void DocumentApi::flipImageWithMask(Layer* layer, Image* image, const Mask* mask
copy_image(image, flippedImage, 0, 0);
}
void DocumentApi::pasteImage(Sprite* sprite, Cel* cel, const Image* src_image, int x, int y, int opacity)
{
ASSERT(cel != NULL);
Image* cel_image = cel->image();
Image* cel_image2 = Image::createCopy(cel_image);
composite_image(cel_image2, src_image, x-cel->x(), y-cel->y(), opacity, BLEND_MODE_NORMAL);
replaceStockImage(sprite, cel->imageIndex(), cel_image2); // TODO fix this, improve, avoid replacing the whole image
}
void DocumentApi::copyToCurrentMask(Mask* mask)
{
ASSERT(m_document->mask());

View File

@ -119,7 +119,6 @@ namespace app {
void clearMask(Cel* cel);
void flipImage(Image* image, const gfx::Rect& bounds, doc::algorithm::FlipType flipType);
void flipImageWithMask(Layer* layer, Image* image, const Mask* mask, doc::algorithm::FlipType flipType);
void pasteImage(Sprite* sprite, Cel* cel, const Image* src_image, int x, int y, int opacity);
// Mask API
void copyToCurrentMask(Mask* mask);

View File

@ -41,6 +41,7 @@
#include "doc/stock.h"
#include "gfx/packing_rects.h"
#include "gfx/size.h"
#include "render/render.h"
#include <cstdio>
#include <fstream>
@ -380,7 +381,7 @@ void DocumentExporter::renderTexture(const Samples& samples, Image* textureImage
docApi.setPixelFormat(
sample.sprite(),
textureImage->pixelFormat(),
DITHERING_NONE);
DitheringMethod::NONE);
}
int x = sample.inTextureBounds().x - sample.trimmedBounds().x;
@ -443,11 +444,15 @@ void DocumentExporter::createDataFile(const Samples& samples, std::ostream& os,
void DocumentExporter::renderSample(const Sample& sample, doc::Image* dst, int x, int y)
{
render::Render render;
if (sample.layer()) {
layer_render(sample.layer(), dst, x, y, sample.frame());
render.renderLayer(dst, sample.layer(), sample.frame(),
gfx::Clip(x, y, sample.sprite()->bounds()));
}
else {
sample.sprite()->render(dst, x, y, sample.frame());
render.renderSprite(dst, sample.sprite(), sample.frame(),
gfx::Clip(x, y, sample.sprite()->bounds()));
}
}

View File

@ -38,8 +38,9 @@
#include "base/scoped_lock.h"
#include "base/shared_ptr.h"
#include "base/string.h"
#include "doc/quantization.h"
#include "doc/doc.h"
#include "render/quantization.h"
#include "render/render.h"
#include "ui/alert.h"
#include <cstring>
@ -563,9 +564,10 @@ void fop_operate(FileOp *fop, IFileOpProgress* progress)
fop->seq.progress_fraction = 1.0f / (double)sprite->totalFrames();
// For each frame in the sprite.
render::Render render;
for (FrameNumber frame(0); frame < sprite->totalFrames(); ++frame) {
// Draw the "frame" in "fop->seq.image"
sprite->render(fop->seq.image, 0, 0, frame);
render.renderSprite(fop->seq.image, sprite, frame);
// Setup the palette.
sprite->getPalette(frame)->copyColorsTo(fop->seq.palette);
@ -672,7 +674,7 @@ void fop_post_load(FileOp* fop)
fop->document->sprite()->getPalettes().size() <= 1 &&
fop->document->sprite()->getPalette(FrameNumber(0))->isBlack()) {
SharedPtr<Palette> palette
(quantization::create_palette_from_rgb(
(render::create_palette_from_rgb(
fop->document->sprite(),
FrameNumber(0), NULL));

View File

@ -28,6 +28,7 @@
#include "app/modules/palettes.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "render/render.h"
#include <cstdio>
@ -160,7 +161,7 @@ bool FliFormat::onLoad(FileOp* fop)
}
/* update the old image and color-map to the new ones to compare later */
copy_image(old, bmp, 0, 0);
copy_image(old, bmp);
memcpy(omap, cmap, 768);
/* update progress */
@ -218,6 +219,7 @@ bool FliFormat::onSave(FileOp* fop)
// Create the bitmaps
base::UniquePtr<Image> bmp(Image::create(IMAGE_INDEXED, sprite->width(), sprite->height()));
base::UniquePtr<Image> old(Image::create(IMAGE_INDEXED, sprite->width(), sprite->height()));
render::Render render;
// Write frame by frame
for (FrameNumber frpos(0);
@ -232,8 +234,7 @@ bool FliFormat::onSave(FileOp* fop)
}
/* render the frame in the bitmap */
clear_image(bmp, 0);
layer_render(sprite->folder(), bmp, 0, 0, frpos);
render.renderSprite(bmp, sprite, frpos);
/* how many times this frame should be written to get the same
time that it has in the sprite */
@ -250,7 +251,7 @@ bool FliFormat::onSave(FileOp* fop)
(unsigned char *)bmp->getPixelAddress(0, 0), cmap, W_ALL);
/* update the old image and color-map to the new ones to compare later */
copy_image(old, bmp, 0, 0);
copy_image(old, bmp);
memcpy(omap, cmap, 768);
}

View File

@ -33,6 +33,8 @@
#include "base/file_handle.h"
#include "base/unique_ptr.h"
#include "doc/doc.h"
#include "render/quantization.h"
#include "render/render.h"
#include "ui/alert.h"
#include "ui/button.h"
@ -495,7 +497,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
break;
case DISPOSAL_METHOD_RESTORE_PREVIOUS:
copy_image(current_image, previous_image, 0, 0);
copy_image(current_image, previous_image);
break;
}
@ -504,7 +506,7 @@ bool GifFormat::onPostLoad(FileOp* fop)
// that we have already updated current_image from
// previous_image).
if (frame_it->disposal_method != DISPOSAL_METHOD_RESTORE_PREVIOUS)
copy_image(previous_image, current_image, 0, 0);
copy_image(previous_image, current_image);
}
fop->document->sprites().add(sprite);
@ -610,14 +612,17 @@ bool GifFormat::onSave(FileOp* fop)
ColorMapObject* image_color_map = NULL;
render::Render render;
render.setBgType(render::BgType::NONE);
// Check if the user wants one optimized palette for all frames.
if (sprite_format != IMAGE_INDEXED &&
gif_options->quantize() == GifOptions::QuantizeAll) {
// Feed the optimizer with all rendered frames.
doc::quantization::PaletteOptimizer optimizer;
render::PaletteOptimizer optimizer;
for (FrameNumber frame_num(0); frame_num<sprite->totalFrames(); ++frame_num) {
clear_image(buffer_image, background_color);
layer_render(sprite->folder(), buffer_image, 0, 0, frame_num);
render.renderSprite(buffer_image, sprite, frame_num);
optimizer.feedWithImage(buffer_image);
}
@ -631,7 +636,7 @@ bool GifFormat::onSave(FileOp* fop)
// If the sprite is RGB or Grayscale, we must to convert it to Indexed on the fly.
if (sprite_format != IMAGE_INDEXED) {
clear_image(buffer_image, background_color);
layer_render(sprite->folder(), buffer_image, 0, 0, frame_num);
render.renderSprite(buffer_image, sprite, frame_num);
switch (gif_options->quantize()) {
case GifOptions::NoQuantize:
@ -644,7 +649,7 @@ bool GifFormat::onSave(FileOp* fop)
std::vector<Image*> imgarray(1);
imgarray[0] = buffer_image;
doc::quantization::create_palette_from_images(imgarray, &current_palette, has_background);
render::create_palette_from_images(imgarray, &current_palette, has_background);
rgbmap.regenerate(&current_palette, transparent_index);
}
break;
@ -653,7 +658,7 @@ bool GifFormat::onSave(FileOp* fop)
break;
}
quantization::convert_pixel_format(
render::convert_pixel_format(
buffer_image,
current_image,
IMAGE_INDEXED,
@ -665,7 +670,7 @@ bool GifFormat::onSave(FileOp* fop)
// If the sprite is Indexed, we can render directly into "current_image".
else {
clear_image(current_image, background_color);
layer_render(sprite->folder(), current_image, 0, 0, frame_num);
render.renderSprite(current_image, sprite, frame_num);
}
if (frame_num == 0) {
@ -773,7 +778,7 @@ bool GifFormat::onSave(FileOp* fop)
}
}
copy_image(previous_image, current_image, 0, 0);
copy_image(previous_image, current_image);
}
return true;
@ -812,7 +817,7 @@ SharedPtr<FormatOptions> GifFormat::onGetFormatOptions(FileOp* fop)
win.interlaced()->setSelected(gif_options->interlaced());
win.dither()->setEnabled(true);
win.dither()->setSelected(gif_options->dithering() == doc::DITHERING_ORDERED);
win.dither()->setSelected(gif_options->dithering() == doc::DitheringMethod::ORDERED);
win.openWindowInForeground();
@ -826,12 +831,12 @@ SharedPtr<FormatOptions> GifFormat::onGetFormatOptions(FileOp* fop)
gif_options->setInterlaced(win.interlaced()->isSelected());
gif_options->setDithering(win.dither()->isSelected() ?
doc::DITHERING_ORDERED:
doc::DITHERING_NONE);
doc::DitheringMethod::ORDERED:
doc::DitheringMethod::NONE);
set_config_int("GIF", "Quantize", gif_options->quantize());
set_config_bool("GIF", "Interlaced", gif_options->interlaced());
set_config_int("GIF", "Dither", gif_options->dithering());
set_config_int("GIF", "Dither", int(gif_options->dithering()));
}
else {
gif_options.reset(NULL);

View File

@ -33,7 +33,7 @@ namespace app {
GifOptions(
Quantize quantize = QuantizeEach,
bool interlaced = false,
DitheringMethod dithering = doc::DITHERING_NONE)
DitheringMethod dithering = doc::DitheringMethod::NONE)
: m_quantize(quantize)
, m_interlaced(interlaced)
, m_dithering(dithering) {

View File

@ -29,6 +29,7 @@
#include "base/cfile.h"
#include "base/file_handle.h"
#include "doc/doc.h"
#include "render/render.h"
namespace app {
@ -280,9 +281,9 @@ bool IcoFormat::onSave(FileOp* fop)
sprite->width(),
sprite->height()));
render::Render render;
for (n=FrameNumber(0); n<num; ++n) {
clear_image(image, 0);
layer_render(sprite->folder(), image, 0, 0, n);
render.renderSprite(image, sprite, n);
bpp = (sprite->pixelFormat() == IMAGE_INDEXED) ? 8 : 24;
bw = (((image->width() * bpp / 8) + 3) / 4) * 4;

View File

@ -21,13 +21,14 @@
#endif
#include "base/unique_ptr.h"
#include "gfx/rect.h"
#include "doc/cel.h"
#include "doc/frame_number.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "doc/stock.h"
#include "gfx/rect.h"
#include "render/render.h"
namespace app {
@ -40,6 +41,7 @@ LayerImage* create_flatten_layer_copy(Sprite* dstSprite, const Layer* srcLayer,
FrameNumber frmin, FrameNumber frmax)
{
base::UniquePtr<LayerImage> flatLayer(new LayerImage(dstSprite));
render::Render render;
for (FrameNumber frame=frmin; frame<=frmax; ++frame) {
// Does this frame have cels to render?
@ -55,9 +57,9 @@ LayerImage* create_flatten_layer_copy(Sprite* dstSprite, const Layer* srcLayer,
base::UniquePtr<Cel> cel(new Cel(frame, imageIndex));
cel->setPosition(bounds.x, bounds.y);
// Clear the image and render this frame.
image->clear(0);
layer_render(srcLayer, image, -bounds.x, -bounds.y, frame);
// Render this frame.
render.renderLayer(image, srcLayer, frame,
gfx::Clip(0, 0, bounds));
// Add the cel (and release the base::UniquePtr).
flatLayer->addCel(cel);

View File

@ -57,23 +57,8 @@ void Preferences::save()
for (auto& pair : m_tools)
pair.second->save();
for (auto& pair : m_docs) {
app::Document* doc = pair.first;
bool specific_file = false;
if (doc && doc->isAssociatedToFile()) {
push_config_state();
set_config_file(docConfigFileName(doc).c_str());
specific_file = true;
}
pair.second->save();
if (specific_file) {
flush_config_file();
pop_config_state();
}
}
for (auto& pair : m_docs)
saveDocPref(pair.first, pair.second);
flush_config_file();
}
@ -98,8 +83,6 @@ ToolPreferences& Preferences::tool(tools::Tool* tool)
DocumentPreferences& Preferences::document(app::Document* document)
{
ASSERT(document != NULL);
auto it = m_docs.find(document);
if (it != m_docs.end()) {
return *it->second;
@ -111,6 +94,18 @@ DocumentPreferences& Preferences::document(app::Document* document)
}
}
void Preferences::onRemoveDocument(doc::Document* doc)
{
ASSERT(dynamic_cast<app::Document*>(doc));
auto it = m_docs.find(static_cast<app::Document*>(doc));
if (it != m_docs.end()) {
saveDocPref(it->first, it->second);
delete it->second;
m_docs.erase(it);
}
}
std::string Preferences::docConfigFileName(app::Document* doc)
{
if (!doc)
@ -127,4 +122,22 @@ std::string Preferences::docConfigFileName(app::Document* doc)
return rf.getFirstOrCreateDefault();
}
void Preferences::saveDocPref(app::Document* doc, app::DocumentPreferences* docPref)
{
bool specific_file = false;
if (doc && doc->isAssociatedToFile()) {
push_config_state();
set_config_file(docConfigFileName(doc).c_str());
specific_file = true;
}
docPref->save();
if (specific_file) {
flush_config_file();
pop_config_state();
}
}
} // namespace app

View File

@ -21,6 +21,7 @@
#pragma once
#include "app/pref/option.h"
#include "doc/documents_observer.h"
#include "generated_pref_types.h"
@ -38,7 +39,8 @@ namespace app {
typedef app::gen::ToolPref ToolPreferences;
typedef app::gen::DocPref DocumentPreferences;
class Preferences : public app::gen::GlobalPref {
class Preferences : public app::gen::GlobalPref
, public doc::DocumentsObserver {
public:
Preferences();
~Preferences();
@ -47,10 +49,14 @@ namespace app {
void save();
ToolPreferences& tool(tools::Tool* tool);
DocumentPreferences& document(app::Document* document);
DocumentPreferences& document(app::Document* doc);
protected:
void onRemoveDocument(doc::Document* doc) override;
private:
std::string docConfigFileName(app::Document* doc);
void saveDocPref(app::Document* doc, app::DocumentPreferences* docPref);
std::map<std::string, app::ToolPreferences*> m_tools;
std::map<app::Document*, app::DocumentPreferences*> m_docs;

View File

@ -23,10 +23,10 @@
#include "app/thumbnail_generator.h"
#include "app/app.h"
#include "app/app_render.h"
#include "app/document.h"
#include "app/file/file.h"
#include "app/file_system.h"
#include "app/util/render.h"
#include "base/bind.h"
#include "base/scoped_lock.h"
#include "base/thread.h"
@ -79,15 +79,14 @@ private:
// The palette to convert the Image
m_palette.reset(new Palette(*sprite->getPalette(FrameNumber(0))));
// Render the 'sprite' in one plain 'image'
RenderEngine renderEngine(m_fop->document,
sprite, NULL, FrameNumber(0));
// Render first frame of the sprite in 'image'
base::UniquePtr<Image> image(Image::create(
sprite->pixelFormat(), sprite->width(), sprite->height()));
doc::ImageBufferPtr thumbnail_buffer(new doc::ImageBuffer);
base::UniquePtr<Image> image(renderEngine.renderSprite(
sprite->bounds(), FrameNumber(0),
Zoom(1, 1), true, false,
thumbnail_buffer));
AppRender render;
render.setupBackground(NULL, image->pixelFormat());
render.setBgType(render::BgType::CHECKED);
render.renderSprite(image, sprite, FrameNumber(0));
// Calculate the thumbnail size
int thumb_w = MAX_THUMBNAIL_SIZE * image->width() / MAX(image->width(), image->height());

View File

@ -22,7 +22,6 @@
#include "app/settings/selection_mode.h"
#include "app/tools/trace_policy.h"
#include "app/zoom.h"
#include "doc/frame_number.h"
#include "filters/tiled_mode.h"
#include "gfx/point.h"
@ -40,6 +39,10 @@ namespace doc {
class Sprite;
}
namespace render {
class Zoom;
}
namespace app {
class Context;
class Document;
@ -129,7 +132,7 @@ namespace app {
virtual gfx::Point getMaskOrigin() = 0;
// Returns the zoom
virtual const Zoom& zoom() = 0;
virtual const render::Zoom& zoom() = 0;
// Return the mouse button which start the tool-loop. It can be used
// by some tools that instead of using the primary/secondary color

View File

@ -29,11 +29,11 @@
#include "app/tools/intertwine.h"
#include "app/tools/point_shape.h"
#include "app/tools/tool_loop.h"
#include "app/util/render.h"
#include "gfx/region.h"
#include "app/ui/editor/editor.h"
#include "doc/image.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
#include "gfx/region.h"
namespace app {
namespace tools {
@ -69,7 +69,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer)
// Prepare preview image (the destination image will be our preview
// in the tool-loop time, so we can see what we are drawing)
RenderEngine::setPreviewImage(
Editor::renderEngine().setPreviewImage(
m_toolLoop->getLayer(),
m_toolLoop->getFrame(),
m_toolLoop->getDstImage());
@ -78,7 +78,7 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer)
void ToolLoopManager::releaseLoop(const Pointer& pointer)
{
// No more preview image
RenderEngine::setPreviewImage(NULL, FrameNumber(0), NULL);
Editor::renderEngine().removePreviewImage();
}
void ToolLoopManager::pressKey(ui::KeyScancode key)

View File

@ -53,7 +53,6 @@
#include "app/ui_context.h"
#include "app/util/boundary.h"
#include "app/util/misc.h"
#include "app/util/render.h"
#include "base/bind.h"
#include "base/unique_ptr.h"
#include "doc/conversion_she.h"
@ -70,6 +69,7 @@ namespace app {
using namespace app::skin;
using namespace gfx;
using namespace ui;
using namespace render;
class EditorPreRenderImpl : public EditorPreRender {
public:
@ -139,7 +139,11 @@ private:
Graphics* m_g;
};
static doc::ImageBufferPtr render_buffer;
// static
doc::ImageBufferPtr Editor::m_renderBuffer;
// static
AppRender Editor::m_renderEngine;
Editor::Editor(Document* document, EditorFlags flags)
: Widget(editor_type())
@ -368,56 +372,83 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
rc.h = clip.y+clip.h-dest_y;
}
// Draw the sprite
if ((rc.w > 0) && (rc.h > 0)) {
RenderEngine renderEngine(m_document, m_sprite, m_layer, m_frame);
if (rc.isEmpty())
return;
// Generate the rendered image
if (!render_buffer)
render_buffer.reset(new doc::ImageBuffer());
// Generate the rendered image
if (!m_renderBuffer)
m_renderBuffer.reset(new doc::ImageBuffer());
base::UniquePtr<Image> rendered(NULL);
try {
// Generate a "expose sprite pixels" notification. This is used by
// tool managers that need to validate this region (copy pixels from
// the original cel) before it can be used by the RenderEngine.
{
gfx::Rect expose = m_zoom.remove(rc);
// If the zoom level is less than 100%, we add extra pixels to
// the exposed area. Those pixels could be shown in the
// rendering process depending on each cel position.
// E.g. when we are drawing in a cel with position < (0,0)
if (m_zoom.scale() < 1.0)
expose.enlarge(1./m_zoom.scale());
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
}
rendered.reset(renderEngine.renderSprite(
rc, m_frame, m_zoom, true,
((m_flags & kShowOnionskin) == kShowOnionskin),
render_buffer));
}
catch (const std::exception& e) {
Console::showException(e);
base::UniquePtr<Image> rendered(NULL);
try {
// Generate a "expose sprite pixels" notification. This is used by
// tool managers that need to validate this region (copy pixels from
// the original cel) before it can be used by the RenderEngine.
{
gfx::Rect expose = m_zoom.remove(rc);
// If the zoom level is less than 100%, we add extra pixels to
// the exposed area. Those pixels could be shown in the
// rendering process depending on each cel position.
// E.g. when we are drawing in a cel with position < (0,0)
if (m_zoom.scale() < 1.0)
expose.enlarge(1./m_zoom.scale());
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
}
if (rendered) {
// Pre-render decorator.
if ((m_flags & kShowDecorators) && m_decorator) {
EditorPreRenderImpl preRender(this, rendered,
Point(-rc.x, -rc.y), m_zoom);
m_decorator->preRenderDecorator(&preRender);
}
// Create a temporary RGB bitmap to draw all to it
rendered.reset(Image::create(IMAGE_RGB, rc.w, rc.h, m_renderBuffer));
m_renderEngine.setupBackground(m_document, rendered->pixelFormat());
m_renderEngine.setOnionskin(render::OnionskinType::NONE, 0, 0, 0, 0);
// Convert the render to a she::Surface
she::Surface* tmp(she::instance()->createRgbaSurface(rc.w, rc.h));
if (tmp->nativeHandle()) {
convert_image_to_surface(rendered, m_sprite->getPalette(m_frame),
tmp, 0, 0, 0, 0, rc.w, rc.h);
g->blit(tmp, 0, 0, dest_x, dest_y, rc.w, rc.h);
if ((m_flags & kShowOnionskin) == kShowOnionskin) {
IDocumentSettings* docSettings = UIContext::instance()
->settings()->getDocumentSettings(m_document);
if (docSettings->getUseOnionskin()) {
m_renderEngine.setOnionskin(
(docSettings->getOnionskinType() == IDocumentSettings::Onionskin_Merge ?
render::OnionskinType::MERGE:
(docSettings->getOnionskinType() == IDocumentSettings::Onionskin_RedBlueTint ?
render::OnionskinType::RED_BLUE_TINT:
render::OnionskinType::NONE)),
docSettings->getOnionskinPrevFrames(),
docSettings->getOnionskinNextFrames(),
docSettings->getOnionskinOpacityBase(),
docSettings->getOnionskinOpacityStep());
}
tmp->dispose();
}
m_renderEngine.setExtraImage(
m_document->getExtraCel(),
m_document->getExtraCelImage(),
m_document->getExtraCelBlendMode(),
m_layer, m_frame);
m_renderEngine.renderSprite(rendered, m_sprite, m_frame,
gfx::Clip(0, 0, rc), m_zoom);
m_renderEngine.removeExtraImage();
}
catch (const std::exception& e) {
Console::showException(e);
}
if (rendered) {
// Pre-render decorator.
if ((m_flags & kShowDecorators) && m_decorator) {
EditorPreRenderImpl preRender(this, rendered,
Point(-rc.x, -rc.y), m_zoom);
m_decorator->preRenderDecorator(&preRender);
}
// Convert the render to a she::Surface
she::Surface* tmp(she::instance()->createRgbaSurface(rc.w, rc.h));
if (tmp->nativeHandle()) {
convert_image_to_surface(rendered, m_sprite->getPalette(m_frame),
tmp, 0, 0, 0, 0, rc.w, rc.h);
g->blit(tmp, 0, 0, dest_x, dest_y, rc.w, rc.h);
}
tmp->dispose();
}
}
@ -721,7 +752,7 @@ void Editor::flashCurrentLayer()
int x, y;
const Image* src_image = loc.image(&x, &y);
if (src_image) {
RenderEngine::setPreviewImage(NULL, FrameNumber(0), NULL);
m_renderEngine.removePreviewImage();
m_document->prepareExtraCel(m_sprite->bounds(), 255);
Image* flash_image = m_document->getExtraCelImage();
@ -1471,7 +1502,7 @@ void Editor::notifyScrollChanged()
// static
ImageBufferPtr Editor::getRenderImageBuffer()
{
return render_buffer;
return m_renderBuffer;
}
void Editor::onSetTiledMode(filters::TiledMode mode)

View File

@ -20,6 +20,7 @@
#define APP_UI_EDITOR_H_INCLUDED
#pragma once
#include "app/app_render.h"
#include "app/color.h"
#include "app/document.h"
#include "app/settings/selection_mode.h"
@ -27,12 +28,12 @@
#include "app/ui/editor/editor_observers.h"
#include "app/ui/editor/editor_state.h"
#include "app/ui/editor/editor_states_history.h"
#include "app/zoom.h"
#include "base/connection.h"
#include "doc/document_observer.h"
#include "doc/frame_number.h"
#include "doc/image_buffer.h"
#include "gfx/fwd.h"
#include "render/zoom.h"
#include "ui/base.h"
#include "ui/timer.h"
#include "ui/widget.h"
@ -123,18 +124,18 @@ namespace app {
void setLayer(const Layer* layer);
void setFrame(FrameNumber frame);
const Zoom& zoom() const { return m_zoom; }
const render::Zoom& zoom() const { return m_zoom; }
int offsetX() const { return m_offset_x; }
int offsetY() const { return m_offset_y; }
int cursorThick() { return m_cursorThick; }
void setZoom(Zoom zoom) { m_zoom = zoom; }
void setZoom(render::Zoom zoom) { m_zoom = zoom; }
void setOffsetX(int x) { m_offset_x = x; }
void setOffsetY(int y) { m_offset_y = y; }
void setDefaultScroll();
void setEditorScroll(const gfx::Point& scroll, bool blit_valid_rgn);
void setEditorZoom(Zoom zoom);
void setEditorZoom(render::Zoom zoom);
// Updates the Editor's view.
void updateEditor();
@ -188,7 +189,7 @@ namespace app {
// Returns true if the cursor is inside the active mask/selection.
bool isInsideSelection();
void setZoomAndCenterInMouse(Zoom zoom,
void setZoomAndCenterInMouse(render::Zoom zoom,
const gfx::Point& mousePos, ZoomBehavior zoomBehavior);
void pasteImage(const Image* image, const gfx::Point& pos);
@ -203,6 +204,8 @@ namespace app {
// E.g. It can be re-used by PreviewCommand
static ImageBufferPtr getRenderImageBuffer();
static AppRender& renderEngine() { return m_renderEngine; }
// in cursor.cpp
static app::Color get_cursor_color();
@ -268,7 +271,7 @@ namespace app {
Sprite* m_sprite; // Active sprite in the editor
Layer* m_layer; // Active layer in the editor
FrameNumber m_frame; // Active frame in the editor
Zoom m_zoom; // Zoom in the editor
render::Zoom m_zoom; // Zoom in the editor
// Drawing cursor
int m_cursorThick;
@ -311,6 +314,9 @@ namespace app {
EditorFlags m_flags;
bool m_secondaryButton;
static doc::ImageBufferPtr m_renderBuffer;
static AppRender m_renderEngine;
};
ui::WidgetType editor_type();

View File

@ -38,6 +38,7 @@
#include "doc/mask.h"
#include "doc/sprite.h"
#include "gfx/region.h"
#include "render/render.h"
namespace app {
@ -455,7 +456,7 @@ void PixelsMovement::stampImage()
gfx::Region modifiedRegion(expand.getDestCanvas()->bounds());
expand.validateDestCanvas(modifiedRegion);
composite_image(
render::composite_image(
expand.getDestCanvas(), image,
-expand.getCel()->x(),
-expand.getCel()->y(),

View File

@ -181,7 +181,7 @@ void SelectBoxState::preRenderDecorator(EditorPreRender* render)
void SelectBoxState::postRenderDecorator(EditorPostRender* render)
{
Editor* editor = render->getEditor();
Zoom zoom = editor->zoom();
render::Zoom zoom = editor->zoom();
gfx::Rect vp = View::getView(editor)->getViewportBounds();
vp.w += zoom.apply(1);
vp.h += zoom.apply(1);

View File

@ -375,7 +375,7 @@ bool StandbyState::onMouseWheel(Editor* editor, MouseMessage* msg)
case WHEEL_ZOOM: {
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
Zoom zoom = editor->zoom();
render::Zoom zoom = editor->zoom();
if (dz < 0) {
while (dz++ < 0)
zoom.in();

View File

@ -260,7 +260,7 @@ public:
bool useMask() override { return m_useMask; }
Mask* getMask() override { return m_mask; }
gfx::Point getMaskOrigin() override { return m_maskOrigin; }
const Zoom& zoom() override { return m_editor->zoom(); }
const render::Zoom& zoom() override { return m_editor->zoom(); }
ToolLoop::Button getMouseButton() override { return m_button; }
int getPrimaryColor() override { return m_primary_color; }
void setPrimaryColor(int color) override { m_primary_color = color; }

View File

@ -57,7 +57,7 @@ bool ZoomingState::onMouseDown(Editor* editor, MouseMessage* msg)
bool ZoomingState::onMouseUp(Editor* editor, MouseMessage* msg)
{
if (!m_moved) {
Zoom zoom = editor->zoom();
render::Zoom zoom = editor->zoom();
if (msg->left())
zoom.in();
@ -77,7 +77,7 @@ bool ZoomingState::onMouseMove(Editor* editor, MouseMessage* msg)
{
gfx::Point pt = (msg->position() - m_startPos);
int length = ABS(pt.x);
Zoom zoom = m_startZoom;
render::Zoom zoom = m_startZoom;
if (length > 0) {
if (pt.x > 0) {

View File

@ -21,8 +21,8 @@
#pragma once
#include "app/ui/editor/editor_state.h"
#include "app/zoom.h"
#include "gfx/point.h"
#include "render/zoom.h"
namespace app {
@ -41,7 +41,7 @@ namespace app {
private:
gfx::Point m_startPos;
Zoom m_startZoom;
render::Zoom m_startZoom;
bool m_moved;
};

View File

@ -245,7 +245,7 @@ void MiniEditorWindow::updateUsingEditor(Editor* editor)
addChild(m_docView);
miniEditor = m_docView->getEditor();
miniEditor->setZoom(Zoom(1, 1));
miniEditor->setZoom(render::Zoom(1, 1));
miniEditor->setState(EditorStatePtr(new EditorState));
layout();
}

View File

@ -24,6 +24,7 @@
#include "app/document.h"
#include "app/document_location.h"
#include "app/modules/editors.h"
#include "app/pref/preferences.h"
#include "app/settings/ui_settings_impl.h"
#include "app/ui/color_bar.h"
#include "app/ui/document_view.h"
@ -47,6 +48,7 @@ UIContext::UIContext()
, m_lastSelectedView(NULL)
{
documents().addObserver(static_cast<UISettingsImpl*>(settings()));
documents().addObserver(&App::instance()->preferences());
ASSERT(m_instance == NULL);
m_instance = this;
@ -58,6 +60,7 @@ UIContext::~UIContext()
m_instance = NULL;
documents().removeObserver(static_cast<UISettingsImpl*>(settings()));
documents().removeObserver(&App::instance()->preferences());
// The context must be empty at this point. (It's to check if the UI
// is working correctly, i.e. closing all files when the user can

View File

@ -45,6 +45,7 @@
#include "app/util/clipboard.h"
#include "app/util/misc.h"
#include "doc/doc.h"
#include "render/quantization.h"
#include "undo/undo_history.h"
#if defined WIN32
@ -275,9 +276,9 @@ void clipboard::paste()
else {
RgbMap* dst_rgbmap = dstSpr->getRgbMap(editor->frame());
src_image = quantization::convert_pixel_format(
src_image = render::convert_pixel_format(
clipboard_image, NULL, dstSpr->pixelFormat(),
DITHERING_NONE, dst_rgbmap, clipboard_palette,
DitheringMethod::NONE, dst_rgbmap, clipboard_palette,
false);
}

View File

@ -290,9 +290,10 @@ void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn)
fill_rect(m_srcImage, rc, m_srcImage->maskColor());
for (const auto& rc : rgnToValidate)
m_srcImage->copy(m_celImage, rc.x, rc.y,
rc.x+m_bounds.x-m_origCelPos.x,
rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h);
m_srcImage->copy(m_celImage,
gfx::Clip(rc.x, rc.y,
rc.x+m_bounds.x-m_origCelPos.x,
rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h));
}
else {
for (const auto& rc : rgnToValidate)
@ -335,9 +336,10 @@ void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn)
fill_rect(m_dstImage, rc, m_dstImage->maskColor());
for (const auto& rc : rgnToValidate)
m_dstImage->copy(src, rc.x, rc.y,
rc.x+m_bounds.x-src_x,
rc.y+m_bounds.y-src_y, rc.w, rc.h);
m_dstImage->copy(src,
gfx::Clip(rc.x, rc.y,
rc.x+m_bounds.x-src_x,
rc.y+m_bounds.y-src_y, rc.w, rc.h));
}
else {
for (const auto& rc : rgnToValidate)
@ -366,7 +368,8 @@ void ExpandCelCanvas::copyValidDestToSourceCanvas(const gfx::Region& rgn)
rgn2.createIntersection(rgn2, m_validSrcRegion);
rgn2.createIntersection(rgn2, m_validDstRegion);
for (const auto& rc : rgn2)
m_srcImage->copy(m_dstImage, rc.x, rc.y, rc.x, rc.y, rc.w, rc.h);
m_srcImage->copy(m_dstImage,
gfx::Clip(rc.x, rc.y, rc.x, rc.y, rc.w, rc.h));
}
void ExpandCelCanvas::copyValidDestToOriginalCel()
@ -374,9 +377,10 @@ void ExpandCelCanvas::copyValidDestToOriginalCel()
// Copy valid destination region to the m_celImage
for (const auto& rc : m_validDstRegion) {
m_celImage->copy(m_dstImage,
rc.x-m_bounds.x+m_origCelPos.x,
rc.y-m_bounds.y+m_origCelPos.y,
rc.x, rc.y, rc.w, rc.h);
gfx::Clip(
rc.x-m_bounds.x+m_origCelPos.x,
rc.y-m_bounds.y+m_origCelPos.y,
rc.x, rc.y, rc.w, rc.h));
}
}

View File

@ -57,7 +57,7 @@ Mask* load_msk_file(const char* filename)
if (image != NULL && (image->pixelFormat() == IMAGE_BITMAP)) {
mask = new Mask();
mask->replace(gfx::Rect(x, y, image->width(), image->height()));
mask->bitmap()->copy(image, 0, 0, 0, 0, image->width(), image->height());
mask->bitmap()->copy(image, gfx::Clip(image->bounds()));
mask->shrink();
}
}

View File

@ -1,753 +0,0 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/util/render.h"
#include "app/color_utils.h"
#include "app/document.h"
#include "app/ini_file.h"
#include "doc/doc.h"
#include "app/settings/document_settings.h"
#include "app/settings/settings.h"
#include "app/ui_context.h"
namespace app {
//////////////////////////////////////////////////////////////////////
// Zoomed merge
template<class DstTraits, class SrcTraits>
class BlenderHelper
{
BLEND_COLOR m_blend_color;
uint32_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_color = SrcTraits::get_blender(blend_mode);
m_mask_color = src->maskColor();
}
inline void operator()(typename DstTraits::pixel_t& scanline,
const typename DstTraits::pixel_t& dst,
const typename SrcTraits::pixel_t& src,
int opacity)
{
if (src != m_mask_color)
scanline = (*m_blend_color)(dst, src, opacity);
else
scanline = dst;
}
};
template<>
class BlenderHelper<RgbTraits, GrayscaleTraits>
{
BLEND_COLOR m_blend_color;
uint32_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_color = RgbTraits::get_blender(blend_mode);
m_mask_color = src->maskColor();
}
inline void operator()(RgbTraits::pixel_t& scanline,
const RgbTraits::pixel_t& dst,
const GrayscaleTraits::pixel_t& src,
int opacity)
{
if (src != m_mask_color) {
int v = graya_getv(src);
scanline = (*m_blend_color)(dst, rgba(v, v, v, graya_geta(src)), opacity);
}
else
scanline = dst;
}
};
template<>
class BlenderHelper<RgbTraits, IndexedTraits>
{
const Palette* m_pal;
int m_blend_mode;
uint32_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_mode = blend_mode;
m_mask_color = src->maskColor();
m_pal = pal;
}
inline void operator()(RgbTraits::pixel_t& scanline,
const RgbTraits::pixel_t& dst,
const IndexedTraits::pixel_t& src,
int opacity)
{
if (m_blend_mode == BLEND_MODE_COPY) {
scanline = m_pal->getEntry(src);
}
else {
if (src != m_mask_color) {
scanline = rgba_blend_normal(dst, m_pal->getEntry(src), opacity);
}
else
scanline = dst;
}
}
};
template<class DstTraits, class SrcTraits>
static void merge_zoomed_image_scale_up(Image* dst, const Image* src, const Palette* pal,
int x, int y, int opacity, int blend_mode, Zoom zoom)
{
BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blend_mode);
int src_x, src_y, src_w, src_h;
int dst_x, dst_y, dst_w, dst_h;
int box_x, box_y, box_w, box_h;
int first_box_w, first_box_h;
int line_h, bottom;
box_w = zoom.apply(1);
box_h = zoom.apply(1);
src_x = 0;
src_y = 0;
src_w = src->width();
src_h = src->height();
dst_x = x;
dst_y = y;
dst_w = zoom.apply(src->width());
dst_h = zoom.apply(src->height());
// clipping...
if (dst_x < 0) {
src_x += zoom.remove(-dst_x);
src_w -= zoom.remove(-dst_x);
dst_w -= (-dst_x);
first_box_w = box_w - ((-dst_x) % box_w);
dst_x = 0;
}
else
first_box_w = 0;
if (dst_y < 0) {
src_y += zoom.remove(-dst_y);
src_h -= zoom.remove(-dst_y);
dst_h -= (-dst_y);
first_box_h = box_h - ((-dst_y) % box_h);
dst_y = 0;
}
else
first_box_h = 0;
if (dst_x+dst_w > dst->width()) {
src_w -= zoom.remove(dst_x+dst_w-dst->width());
dst_w = dst->width() - dst_x;
}
if (dst_y+dst_h > dst->height()) {
src_h -= zoom.remove(dst_y+dst_h-dst->height());
dst_h = dst->height() - dst_y;
}
if ((src_w <= 0) || (src_h <= 0) ||
(dst_w <= 0) || (dst_h <= 0))
return;
bottom = dst_y+dst_h-1;
// the scanline variable is used to blend src/dst pixels one time for each pixel
typedef std::vector<typename DstTraits::pixel_t> Scanline;
Scanline scanline(src_w);
typename Scanline::iterator scanline_it;
#ifdef _DEBUG
typename Scanline::iterator scanline_end = scanline.end();
#endif
// Lock all necessary bits
const LockImageBits<SrcTraits> srcBits(src, gfx::Rect(src_x, src_y, src_w, src_h));
LockImageBits<DstTraits> dstBits(dst, gfx::Rect(dst_x, dst_y, dst_w, dst_h));
typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin();
#ifdef _DEBUG
typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end();
#endif
typename LockImageBits<DstTraits>::iterator dst_it, dst_end;
// For each line to draw of the source image...
for (y=0; y<src_h; ++y) {
dst_it = dstBits.begin_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
dst_end = dstBits.end_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
// Read 'src' and 'dst' and blend them, put the result in `scanline'
scanline_it = scanline.begin();
for (x=0; x<src_w; ++x) {
ASSERT(src_it >= srcBits.begin() && src_it < src_end);
ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end);
ASSERT(scanline_it >= scanline.begin() && scanline_it < scanline_end);
blender(*scanline_it, *dst_it, *src_it, opacity);
++src_it;
int delta;
if ((x == 0) && (first_box_w > 0))
delta = first_box_w;
else
delta = box_w;
while (dst_it != dst_end && delta-- > 0)
++dst_it;
++scanline_it;
}
// Get the 'height' of the line to be painted in 'dst'
if ((y == 0) && (first_box_h > 0))
line_h = first_box_h;
else
line_h = box_h;
// Draw the line in 'dst'
for (box_y=0; box_y<line_h; ++box_y) {
dst_it = dstBits.begin_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
dst_end = dstBits.end_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
scanline_it = scanline.begin();
x = 0;
// first pixel
if (first_box_w > 0) {
for (box_x=0; box_x<first_box_w; ++box_x) {
ASSERT(scanline_it != scanline_end);
ASSERT(dst_it != dst_end);
*dst_it = *scanline_it;
++dst_it;
if (dst_it == dst_end)
goto done_with_line;
}
++scanline_it;
++x;
}
// the rest of the line
for (; x<src_w; ++x) {
for (box_x=0; box_x<box_w; ++box_x) {
ASSERT(dst_it != dst_end);
*dst_it = *scanline_it;
++dst_it;
if (dst_it == dst_end)
goto done_with_line;
}
++scanline_it;
}
done_with_line:;
if (++dst_y > bottom)
goto done_with_blit;
}
// go to the next line in the source image
++src_y;
}
done_with_blit:;
}
template<class DstTraits, class SrcTraits>
static void merge_zoomed_image_scale_down(Image* dst, const Image* src, const Palette* pal,
int x, int y, int opacity, int blend_mode, Zoom zoom)
{
BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blend_mode);
int src_x, src_y, src_w, src_h;
int dst_x, dst_y, dst_w, dst_h;
int unbox_w, unbox_h;
int bottom;
unbox_w = zoom.remove(1);
unbox_h = zoom.remove(1);
src_x = 0;
src_y = 0;
src_w = src->width();
src_h = src->height();
dst_x = x;
dst_y = y;
dst_w = zoom.apply(src->width());
dst_h = zoom.apply(src->height());
// clipping...
if (dst_x < 0) {
src_x += zoom.remove(-dst_x);
src_w -= zoom.remove(-dst_x);
dst_w -= (-dst_x);
dst_x = 0;
}
if (dst_y < 0) {
src_y += zoom.remove(-dst_y);
src_h -= zoom.remove(-dst_y);
dst_h -= (-dst_y);
dst_y = 0;
}
if (dst_x+dst_w > dst->width()) {
src_w -= zoom.remove(dst_x+dst_w-dst->width());
dst_w = dst->width() - dst_x;
}
if (dst_y+dst_h > dst->height()) {
src_h -= zoom.remove(dst_y+dst_h-dst->height());
dst_h = dst->height() - dst_y;
}
src_w = zoom.remove(zoom.apply(src_w));
src_h = zoom.remove(zoom.apply(src_h));
if ((src_w <= 0) || (src_h <= 0) ||
(dst_w <= 0) || (dst_h <= 0))
return;
bottom = dst_y+dst_h-1;
// Lock all necessary bits
const LockImageBits<SrcTraits> srcBits(src, gfx::Rect(src_x, src_y, src_w, src_h));
LockImageBits<DstTraits> dstBits(dst, gfx::Rect(dst_x, dst_y, dst_w, dst_h));
typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin();
typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end();
typename LockImageBits<DstTraits>::iterator dst_it, dst_end;
// For each line to draw of the source image...
for (y=0; y<src_h; y+=unbox_h) {
dst_it = dstBits.begin_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
dst_end = dstBits.end_area(gfx::Rect(dst_x, dst_y, dst_w, 1));
for (x=0; x<src_w; x+=unbox_w) {
ASSERT(src_it >= srcBits.begin() && src_it < src_end);
ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end);
blender(*dst_it, *dst_it, *src_it, opacity);
// Skip source pixels
for (int delta=0; delta < unbox_w && src_it != src_end; ++delta)
++src_it;
++dst_it;
}
if (++dst_y > bottom)
break;
// Skip lines
for (int delta=0; delta < src_w * (unbox_h-1) && src_it != src_end; ++delta)
++src_it;
}
}
template<class DstTraits, class SrcTraits>
static void merge_zoomed_image(Image* dst, const Image* src, const Palette* pal,
int x, int y, int opacity, int blend_mode, Zoom zoom)
{
if (zoom.scale() >= 1.0)
merge_zoomed_image_scale_up<DstTraits, SrcTraits>(dst, src, pal, x, y, opacity, blend_mode, zoom);
else
merge_zoomed_image_scale_down<DstTraits, SrcTraits>(dst, src, pal, x, y, opacity, blend_mode, zoom);
}
//////////////////////////////////////////////////////////////////////
// Render Engine
static RenderEngine::CheckedBgType checked_bg_type;
static bool checked_bg_zoom;
static app::Color checked_bg_color1;
static app::Color checked_bg_color2;
static int global_opacity = 255;
static const Layer* selected_layer = NULL;
static FrameNumber selected_frame(0);
static Image* preview_image = NULL;
// static
void RenderEngine::loadConfig()
{
checked_bg_type = (CheckedBgType)get_config_int("Options", "CheckedBgType",
(int)RenderEngine::CHECKED_BG_16X16);
checked_bg_zoom = get_config_bool("Options", "CheckedBgZoom", true);
checked_bg_color1 = get_config_color("Options", "CheckedBgColor1", app::Color::fromRgb(128, 128, 128));
checked_bg_color2 = get_config_color("Options", "CheckedBgColor2", app::Color::fromRgb(192, 192, 192));
}
// static
RenderEngine::CheckedBgType RenderEngine::getCheckedBgType()
{
return checked_bg_type;
}
// static
void RenderEngine::setCheckedBgType(CheckedBgType type)
{
checked_bg_type = type;
set_config_int("Options", "CheckedBgType", (int)type);
}
// static
bool RenderEngine::getCheckedBgZoom()
{
return checked_bg_zoom;
}
// static
void RenderEngine::setCheckedBgZoom(bool state)
{
checked_bg_zoom = state;
set_config_bool("Options", "CheckedBgZoom", state);
}
// static
app::Color RenderEngine::getCheckedBgColor1()
{
return checked_bg_color1;
}
// static
void RenderEngine::setCheckedBgColor1(const app::Color& color)
{
checked_bg_color1 = color;
set_config_color("Options", "CheckedBgColor1", color);
}
// static
app::Color RenderEngine::getCheckedBgColor2()
{
return checked_bg_color2;
}
// static
void RenderEngine::setCheckedBgColor2(const app::Color& color)
{
checked_bg_color2 = color;
set_config_color("Options", "CheckedBgColor2", color);
}
//////////////////////////////////////////////////////////////////////
RenderEngine::RenderEngine(const Document* document,
const Sprite* sprite,
const Layer* currentLayer,
FrameNumber currentFrame)
: m_document(document)
, m_sprite(sprite)
, m_currentLayer(currentLayer)
, m_currentFrame(currentFrame)
{
}
// static
void RenderEngine::setPreviewImage(const Layer* layer, FrameNumber frame, Image* image)
{
selected_layer = layer;
selected_frame = frame;
preview_image = image;
}
Image* RenderEngine::renderSprite(
const gfx::Rect& zoomedRect,
FrameNumber frame, Zoom zoom,
bool draw_tiled_bg,
bool enable_onionskin,
ImageBufferPtr& buffer)
{
void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom);
const LayerImage* background = m_sprite->backgroundLayer();
bool need_checked_bg = (background != NULL ? !background->isVisible(): true);
uint32_t bg_color = 0;
Image *image;
switch (m_sprite->pixelFormat()) {
case IMAGE_RGB:
zoomed_func = merge_zoomed_image<RgbTraits, RgbTraits>;
break;
case IMAGE_GRAYSCALE:
zoomed_func = merge_zoomed_image<RgbTraits, GrayscaleTraits>;
break;
case IMAGE_INDEXED:
zoomed_func = merge_zoomed_image<RgbTraits, IndexedTraits>;
if (!need_checked_bg)
bg_color = m_sprite->getPalette(frame)->getEntry(m_sprite->transparentColor());
break;
default:
return NULL;
}
// Create a temporary RGB bitmap to draw all to it
image = Image::create(IMAGE_RGB, zoomedRect.w, zoomedRect.h, buffer);
if (!image)
return NULL;
// Draw checked background
if (need_checked_bg && draw_tiled_bg)
renderCheckedBackground(image, zoomedRect.x, zoomedRect.y, zoom);
else
clear_image(image, bg_color);
// Draw the current frame.
global_opacity = 255;
renderLayer(m_sprite->folder(), image,
zoomedRect.x, zoomedRect.y,
frame, zoom, zoomed_func, true, true, -1);
// Onion-skin feature: Draw previous/next frames with different
// opacity (<255) (it is the onion-skinning)
IDocumentSettings* docSettings = UIContext::instance()
->settings()->getDocumentSettings(m_document);
if (enable_onionskin & docSettings->getUseOnionskin()) {
int prevs = docSettings->getOnionskinPrevFrames();
int nexts = docSettings->getOnionskinNextFrames();
int opacity_base = docSettings->getOnionskinOpacityBase();
int opacity_step = docSettings->getOnionskinOpacityStep();
for (FrameNumber f=frame.previous(prevs); f <= frame.next(nexts); ++f) {
if (f == frame || f < 0 || f > m_sprite->lastFrame())
continue;
else if (f < frame)
global_opacity = opacity_base - opacity_step * ((frame - f)-1);
else
global_opacity = opacity_base - opacity_step * ((f - frame)-1);
if (global_opacity > 0) {
global_opacity = MID(0, global_opacity, 255);
int blend_mode = -1;
if (docSettings->getOnionskinType() == IDocumentSettings::Onionskin_Merge)
blend_mode = BLEND_MODE_NORMAL;
else if (docSettings->getOnionskinType() == IDocumentSettings::Onionskin_RedBlueTint)
blend_mode = (f < frame ? BLEND_MODE_RED_TINT: BLEND_MODE_BLUE_TINT);
renderLayer(m_sprite->folder(), image,
zoomedRect.x, zoomedRect.y, f, zoom, zoomed_func,
true, true, blend_mode);
}
}
}
return image;
}
// static
void RenderEngine::renderCheckedBackground(Image* image,
int source_x, int source_y, Zoom zoom)
{
int x, y, u, v;
int tile_w = 16;
int tile_h = 16;
int c1 = color_utils::color_for_image(checked_bg_color1, image->pixelFormat());
int c2 = color_utils::color_for_image(checked_bg_color2, image->pixelFormat());
switch (checked_bg_type) {
case CHECKED_BG_16X16:
tile_w = 16;
tile_h = 16;
break;
case CHECKED_BG_8X8:
tile_w = 8;
tile_h = 8;
break;
case CHECKED_BG_4X4:
tile_w = 4;
tile_h = 4;
break;
case CHECKED_BG_2X2:
tile_w = 2;
tile_h = 2;
break;
}
if (checked_bg_zoom) {
tile_w = zoom.apply(tile_w);
tile_h = zoom.apply(tile_h);
}
// Tile size
if (tile_w < zoom.apply(1)) tile_w = zoom.apply(1);
if (tile_h < zoom.apply(1)) tile_h = zoom.apply(1);
if (tile_w < 1) tile_w = 1;
if (tile_h < 1) tile_h = 1;
// Tile position (u,v) is the number of tile we start in (source_x,source_y) coordinate
u = (source_x / tile_w);
v = (source_y / tile_h);
// Position where we start drawing the first tile in "image"
int x_start = -(source_x % tile_w);
int y_start = -(source_y % tile_h);
// Draw checked background (tile by tile)
int u_start = u;
for (y=y_start-tile_h; y<image->height()+tile_h; y+=tile_h) {
for (x=x_start-tile_w; x<image->width()+tile_w; x+=tile_w) {
fill_rect(image, x, y, x+tile_w-1, y+tile_h-1,
(((u+v))&1)? c1: c2);
++u;
}
u = u_start;
++v;
}
}
// static
void RenderEngine::renderImage(Image* rgb_image, Image* src_image, const Palette* pal,
int x, int y, Zoom zoom)
{
void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom);
ASSERT(rgb_image->pixelFormat() == IMAGE_RGB && "renderImage accepts RGB destination images only");
switch (src_image->pixelFormat()) {
case IMAGE_RGB:
zoomed_func = merge_zoomed_image<RgbTraits, RgbTraits>;
break;
case IMAGE_GRAYSCALE:
zoomed_func = merge_zoomed_image<RgbTraits, GrayscaleTraits>;
break;
case IMAGE_INDEXED:
zoomed_func = merge_zoomed_image<RgbTraits, IndexedTraits>;
break;
default:
return;
}
(*zoomed_func)(rgb_image, src_image, pal, x, y, 255, BLEND_MODE_NORMAL, zoom);
}
void RenderEngine::renderLayer(
const Layer* layer,
Image *image,
int source_x, int source_y,
FrameNumber frame, Zoom zoom,
void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom),
bool render_background,
bool render_transparent,
int blend_mode)
{
// we can't read from this layer
if (!layer->isVisible())
return;
switch (layer->type()) {
case ObjectType::LayerImage: {
if ((!render_background && layer->isBackground()) ||
(!render_transparent && !layer->isBackground()))
break;
const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame);
if (cel != NULL) {
Image* src_image;
// Is the 'preview_image' set to be used with this layer?
if ((selected_layer == layer) &&
(selected_frame == frame) &&
(preview_image != NULL)) {
src_image = preview_image;
}
// If not, we use the original cel-image from the images' stock
else {
src_image = cel->image();
}
if (src_image) {
int t, output_opacity;
output_opacity = MID(0, cel->opacity(), 255);
output_opacity = INT_MULT(output_opacity, global_opacity, t);
ASSERT(src_image->maskColor() == m_sprite->transparentColor());
(*zoomed_func)(image, src_image, m_sprite->getPalette(frame),
zoom.apply(cel->x()) - source_x,
zoom.apply(cel->y()) - source_y,
output_opacity,
(blend_mode < 0 ?
static_cast<const LayerImage*>(layer)->getBlendMode():
blend_mode),
zoom);
}
}
break;
}
case ObjectType::LayerFolder: {
LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin();
LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
for (; it != end; ++it) {
renderLayer(*it, image,
source_x, source_y,
frame, zoom, zoomed_func,
render_background,
render_transparent,
blend_mode);
}
break;
}
}
// Draw extras
if (m_document->getExtraCel() &&
layer == m_currentLayer &&
frame == m_currentFrame) {
Cel* extraCel = m_document->getExtraCel();
if (extraCel->opacity() > 0) {
Image* extraImage = m_document->getExtraCelImage();
(*zoomed_func)(image, extraImage, m_sprite->getPalette(frame),
zoom.apply(extraCel->x()) - source_x,
zoom.apply(extraCel->y()) - source_y,
extraCel->opacity(),
m_document->getExtraCelBlendMode(), zoom);
}
}
}
} // namespace app

View File

@ -1,110 +0,0 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef APP_UTIL_RENDER_H_INCLUDED
#define APP_UTIL_RENDER_H_INCLUDED
#pragma once
#include "app/color.h"
#include "app/zoom.h"
#include "doc/frame_number.h"
#include "doc/image_buffer.h"
#include "gfx/rect.h"
namespace doc {
class Image;
class Layer;
class Palette;
class Sprite;
}
namespace app {
class Document;
using namespace doc;
class RenderEngine {
public:
RenderEngine(const Document* document,
const Sprite* sprite,
const Layer* currentLayer,
FrameNumber currentFrame);
//////////////////////////////////////////////////////////////////////
// Checked background configuration
enum CheckedBgType { CHECKED_BG_16X16,
CHECKED_BG_8X8,
CHECKED_BG_4X4,
CHECKED_BG_2X2 };
static void loadConfig();
static CheckedBgType getCheckedBgType();
static void setCheckedBgType(CheckedBgType type);
static bool getCheckedBgZoom();
static void setCheckedBgZoom(bool state);
static app::Color getCheckedBgColor1();
static void setCheckedBgColor1(const app::Color& color);
static app::Color getCheckedBgColor2();
static void setCheckedBgColor2(const app::Color& color);
//////////////////////////////////////////////////////////////////////
// Preview image
static void setPreviewImage(const Layer* layer, FrameNumber frame, Image* drawable);
//////////////////////////////////////////////////////////////////////
// Main function used by sprite-editors to render the sprite.
// Draws the given sprite frame in a new image and return it.
// Note: zoomedRect must have the zoom applied (zoomedRect = zoom.apply(spriteRect)).
Image* renderSprite(const gfx::Rect& zoomedRect,
FrameNumber frame, Zoom zoom,
bool draw_tiled_bg,
bool enable_onionskin,
ImageBufferPtr& buffer);
//////////////////////////////////////////////////////////////////////
// Extra functions
static void renderCheckedBackground(Image* image,
int source_x, int source_y,
Zoom zoom);
static void renderImage(Image* rgb_image, Image* src_image, const Palette* pal,
int x, int y, Zoom zoom);
private:
void renderLayer(
const Layer* layer,
Image* image,
int source_x, int source_y,
FrameNumber frame, Zoom zoom,
void (*zoomed_func)(Image*, const Image*, const Palette*, int, int, int, int, Zoom),
bool render_background,
bool render_transparent,
int blend_mode);
const Document* m_document;
const Sprite* m_sprite;
const Layer* m_currentLayer;
FrameNumber m_currentFrame;
};
} // namespace app
#endif

View File

@ -36,7 +36,6 @@ add_library(doc-lib
palette.cpp
palette_io.cpp
primitives.cpp
quantization.cpp
rgbmap.cpp
sprite.cpp
sprites.cpp

View File

@ -63,7 +63,7 @@ void flip_image_with_mask(Image* image, const Mask* mask, FlipType flipType, int
for (int y=bounds.y; y<bounds.y+bounds.h; ++y) {
// Copy the current row.
copy_image(originalRow, image, -bounds.x, -y);
originalRow->copy(image, gfx::Clip(0, 0, bounds.x, y, bounds.w, 1));
int u = bounds.x+bounds.w-1;
for (int x=bounds.x; x<bounds.x+bounds.w; ++x, --u) {
@ -82,7 +82,7 @@ void flip_image_with_mask(Image* image, const Mask* mask, FlipType flipType, int
for (int x=bounds.x; x<bounds.x+bounds.w; ++x) {
// Copy the current column.
copy_image(originalCol, image, -x, -bounds.y);
originalCol->copy(image, gfx::Clip(0, 0, x, bounds.y, 1, bounds.h));
int v = bounds.y+bounds.h-1;
for (int y=bounds.y; y<bounds.y+bounds.h; ++y, --v) {

View File

@ -74,7 +74,7 @@ private:
void scale_image(Image *dst, Image *src, int x, int y, int w, int h)
{
if (w == src->width() && src->height() == h)
composite_image(dst, src, x, y, 255, BLEND_MODE_NORMAL);
dst->copy(src, gfx::Clip(x, y, 0, 0, w, h));
else {
switch (dst->pixelFormat()) {

View File

@ -176,12 +176,12 @@ void rotsprite_image(Image* bmp, Image* spr,
bmp_copy->clear(bmp->maskColor());
spr_copy->clear(maskColor);
spr_copy->copy(spr, 0, 0, 0, 0, spr->width(), spr->height());
spr_copy->copy(spr, gfx::Clip(spr->bounds()));
for (int i=0; i<3; ++i) {
tmp_copy->clear(maskColor);
image_scale2x(tmp_copy, spr_copy, spr->width()*(1<<i), spr->height()*(1<<i));
spr_copy->copy(tmp_copy, 0, 0, 0, 0, tmp_copy->width(), tmp_copy->height());
spr_copy->copy(tmp_copy, gfx::Clip(tmp_copy->bounds()));
}
doc::algorithm::parallelogram(bmp_copy, spr_copy,

View File

@ -33,9 +33,8 @@ BLEND_COLOR graya_blenders[] =
graya_blend_blackandwhite,
};
/**********************************************************************/
/* RGB blenders */
/**********************************************************************/
//////////////////////////////////////////////////////////////////////
// RGB blenders
int rgba_blend_normal(int back, int front, int opacity)
{
@ -209,9 +208,8 @@ int rgba_blend_blackandwhite(int back, int front, int opacity)
return rgba(D_v, D_v, D_v, 255);
}
/**********************************************************************/
/* Grayscale blenders */
/**********************************************************************/
//////////////////////////////////////////////////////////////////////
// Grayscale blenders
int graya_blend_normal(int back, int front, int opacity)
{
@ -308,4 +306,12 @@ int graya_blend_blackandwhite(int back, int front, int opacity)
return graya(D_k, 255);
}
//////////////////////////////////////////////////////////////////////
// Indexed blenders
int indexed_blend_direct(int back, int front, int opacity)
{
return front;
}
} // namespace doc

View File

@ -42,6 +42,8 @@ namespace doc {
int graya_blend_merge(int back, int front, int opacity);
int graya_blend_blackandwhite(int back, int front, int opacity);
int indexed_blend_direct(int back, int front, int opacity);
} // namespace doc
#endif

View File

@ -11,9 +11,9 @@
namespace doc {
// Dithering methods
enum DitheringMethod {
DITHERING_NONE,
DITHERING_ORDERED,
enum class DitheringMethod {
NONE,
ORDERED,
};
} // namespace doc

View File

@ -25,7 +25,6 @@
#include "doc/pixel_format.h"
#include "doc/primitives.h"
#include "doc/primitives_fast.h"
#include "doc/quantization.h"
#include "doc/rgbmap.h"
#include "doc/sprite.h"
#include "doc/stock.h"

View File

@ -10,6 +10,8 @@
namespace doc {
typedef int frame_t;
class FrameNumber {
public:
FrameNumber() : m_value(0) { }

View File

@ -8,13 +8,14 @@
#define DOC_IMAGE_H_INCLUDED
#pragma once
#include "gfx/rect.h"
#include "gfx/size.h"
#include "doc/blend.h"
#include "doc/color.h"
#include "doc/image_buffer.h"
#include "doc/object.h"
#include "doc/pixel_format.h"
#include "gfx/clip.h"
#include "gfx/rect.h"
#include "gfx/size.h"
namespace doc {
@ -72,8 +73,7 @@ namespace doc {
virtual color_t getPixel(int x, int y) const = 0;
virtual void putPixel(int x, int y, color_t color) = 0;
virtual void clear(color_t color) = 0;
virtual void copy(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) = 0;
virtual void merge(const Image* _src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) = 0;
virtual void copy(const Image* src, gfx::Clip area) = 0;
virtual void drawHLine(int x1, int y, int x2, color_t color) = 0;
virtual void fillRect(int x1, int y1, int x2, int y2, color_t color) = 0;
virtual void blendRect(int x1, int y1, int x2, int y2, color_t color, int opacity) = 0;

View File

@ -103,57 +103,28 @@ namespace doc {
*it = color;
}
void copy(const Image* _src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) override {
void copy(const Image* _src, gfx::Clip area) override {
const ImageImpl<Traits>* src = (const ImageImpl<Traits>*)_src;
address_t src_address;
address_t dst_address;
int bytes;
if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h))
if (!area.clip(width(), height(), src->width(), src->height()))
return;
// Copy process
bytes = Traits::getRowStrideBytes(w);
bytes = Traits::getRowStrideBytes(area.size.w);
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
src_address = src->address(src_x, src_y);
dst_address = address(dst_x, dst_y);
for (int end_y=area.dst.y+area.size.h;
area.dst.y<end_y;
++area.dst.y, ++area.src.y) {
src_address = src->address(area.src.x, area.src.y);
dst_address = address(area.dst.x, area.dst.y);
memcpy(dst_address, src_address, bytes);
}
}
void merge(const Image* _src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) override {
BLEND_COLOR blender = Traits::get_blender(blend_mode);
const ImageImpl<Traits>* src = (const ImageImpl<Traits>*)_src;
ImageImpl<Traits>* dst = this;
address_t src_address;
address_t dst_address;
uint32_t mask_color = src->maskColor();
// nothing to do
if (!opacity)
return;
if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h))
return;
// Merge process
int end_x = dst_x+w;
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
src_address = src->address(src_x, src_y);
dst_address = dst->address(dst_x, dst_y);
for (int x=dst_x; x<end_x; ++x) {
if (*src_address != mask_color)
*dst_address = (*blender)(*dst_address, *src_address, opacity);
++dst_address;
++src_address;
}
}
}
void drawHLine(int x1, int y, int x2, color_t color) override {
LockImageBits<Traits> bits(this, gfx::Rect(x1, y, x2 - x1 + 1, 1));
typename LockImageBits<Traits>::iterator it(bits.begin());
@ -279,61 +250,20 @@ namespace doc {
}
template<>
inline void ImageImpl<IndexedTraits>::merge(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) {
if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h))
return;
address_t src_address;
address_t dst_address;
int end_x = dst_x+w;
// Direct copy
if (blend_mode == BLEND_MODE_COPY) {
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
src_address = src->getPixelAddress(src_x, src_y);
dst_address = getPixelAddress(dst_x, dst_y);
for (int x=dst_x; x<end_x; ++x) {
*dst_address = (*src_address);
++dst_address;
++src_address;
}
}
}
// With mask
else {
int mask_color = src->maskColor();
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
src_address = src->getPixelAddress(src_x, src_y);
dst_address = getPixelAddress(dst_x, dst_y);
for (int x=dst_x; x<end_x; ++x) {
if (*src_address != mask_color)
*dst_address = (*src_address);
++dst_address;
++src_address;
}
}
}
}
template<>
inline void ImageImpl<BitmapTraits>::copy(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) {
if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h))
inline void ImageImpl<BitmapTraits>::copy(const Image* src, gfx::Clip area) {
if (!area.clip(width(), height(), src->width(), src->height()))
return;
// Copy process
ImageConstIterator<BitmapTraits> src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y);
ImageIterator<BitmapTraits> dst_it(this, gfx::Rect(dst_x, dst_y, w, h), dst_x, dst_y);
ImageConstIterator<BitmapTraits> src_it(src, area.srcBounds(), area.src.x, area.src.y);
ImageIterator<BitmapTraits> dst_it(this, area.dstBounds(), area.dst.x, area.dst.y);
int end_x = dst_x+w;
int end_x = area.dst.x+area.size.w;
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
for (int x=dst_x; x<end_x; ++x) {
for (int end_y=area.dst.y+area.size.h;
area.dst.y<end_y;
++area.dst.y, ++area.src.y) {
for (int x=area.dst.x; x<end_x; ++x) {
*dst_it = *src_it;
++src_it;
++dst_it;
@ -341,27 +271,6 @@ namespace doc {
}
}
template<>
inline void ImageImpl<BitmapTraits>::merge(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) {
if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h))
return;
// Merge process
ImageConstIterator<BitmapTraits> src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y);
ImageIterator<BitmapTraits> dst_it(this, gfx::Rect(dst_x, dst_y, w, h), dst_x, dst_y);
int end_x = dst_x+w;
for (int end_y=dst_y+h; dst_y<end_y; ++dst_y, ++src_y) {
for (int x=dst_x; x<end_x; ++x) {
if (*dst_it != 0)
*dst_it = *src_it;
++src_it;
++dst_it;
}
}
}
} // namespace doc
#endif

View File

@ -94,6 +94,11 @@ namespace doc {
{
return bytes_per_pixel * pixels_per_row;
}
static inline BLEND_COLOR get_blender(int blend_mode)
{
return indexed_blend_direct;
}
};
struct BitmapTraits {

View File

@ -76,6 +76,11 @@ Layer* Layer::getNext() const
return NULL;
}
Cel* Layer::cel(frame_t frame) const
{
return NULL;
}
//////////////////////////////////////////////////////////////////////
// LayerImage class
@ -124,6 +129,11 @@ void LayerImage::destroyAllCels()
m_cels.clear();
}
Cel* LayerImage::cel(frame_t frame) const
{
return const_cast<Cel*>(getCel(FrameNumber(frame)));
}
void LayerImage::getCels(CelList& cels) const
{
CelConstIterator it = getCelBegin();
@ -300,46 +310,4 @@ void LayerFolder::stackLayer(Layer* layer, Layer* after)
m_layers.push_front(layer);
}
void layer_render(const Layer* layer, Image* image, int x, int y, FrameNumber frame)
{
if (!layer->isVisible())
return;
switch (layer->type()) {
case ObjectType::LayerImage: {
const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame);
Image* src_image;
if (cel) {
ASSERT((cel->imageIndex() >= 0) &&
(cel->imageIndex() < layer->sprite()->stock()->size()));
src_image = cel->image();
ASSERT(src_image != NULL);
ASSERT(src_image->maskColor() == layer->sprite()->transparentColor());
composite_image(image, src_image,
cel->x() + x,
cel->y() + y,
MID(0, cel->opacity(), 255),
static_cast<const LayerImage*>(layer)->getBlendMode());
}
break;
}
case ObjectType::LayerFolder: {
LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin();
LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
for (; it != end; ++it)
layer_render(*it, image, x, y, frame);
break;
}
}
}
} // namespace doc

View File

@ -87,6 +87,7 @@ namespace doc {
m_flags = LayerFlags(int(m_flags) & ~int(flags));
}
virtual Cel* cel(frame_t frame) const;
virtual void getCels(CelList& cels) const = 0;
private:
@ -116,6 +117,7 @@ namespace doc {
void moveCel(Cel *cel, FrameNumber frame);
const Cel* getCel(FrameNumber frame) const;
Cel* getCel(FrameNumber frame);
Cel* cel(frame_t frame) const override;
void getCels(CelList& cels) const override;
Cel* getLastCel() const;
@ -165,8 +167,6 @@ namespace doc {
LayerList m_layers;
};
void layer_render(const Layer* layer, Image *image, int x, int y, FrameNumber frame);
} // namespace doc
#endif

View File

@ -97,7 +97,7 @@ void Mask::copyFrom(const Mask* sourceMask)
add(sourceMask->bounds());
// And copy the "mask" bitmap
copy_image(m_bitmap, sourceMask->m_bitmap, 0, 0);
copy_image(m_bitmap, sourceMask->m_bitmap);
}
}

View File

@ -66,6 +66,10 @@ namespace doc {
FrameNumber frame() const { return m_frame; }
void setFrame(FrameNumber frame);
color_t entry(int i) const {
ASSERT(i >= 0 && i < size());
return m_colors[i];
}
color_t getEntry(int i) const {
ASSERT(i >= 0 && i < size());
return m_colors[i];

View File

@ -65,14 +65,14 @@ void clear_image(Image* image, color_t color)
image->clear(color);
}
void copy_image(Image* dst, const Image* src, int x, int y)
void copy_image(Image* dst, const Image* src)
{
dst->copy(src, x, y, 0, 0, src->width(), src->height());
dst->copy(src, gfx::Clip(0, 0, 0, 0, src->width(), src->height()));
}
void composite_image(Image* dst, const Image* src, int x, int y, int opacity, int blend_mode)
void copy_image(Image* dst, const Image* src, int x, int y)
{
dst->merge(src, x, y, 0, 0, src->width(), src->height(), opacity, blend_mode);
dst->copy(src, gfx::Clip(x, y, 0, 0, src->width(), src->height()));
}
Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer)
@ -84,7 +84,7 @@ Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, co
trim->setMaskColor(image->maskColor());
clear_image(trim, bg);
trim->copy(image, 0, 0, x, y, w, h);
trim->copy(image, gfx::Clip(0, 0, x, y, w, h));
return trim;
}

View File

@ -23,9 +23,8 @@ namespace doc {
void clear_image(Image* image, color_t bg);
void copy_image(Image* dst, const Image* src);
void copy_image(Image* dst, const Image* src, int x, int y);
void composite_image(Image* dst, const Image* src, int x, int y, int opacity, int blend_mode);
Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer = ImageBufferPtr());
void rotate_image(const Image* src, Image* dst, int angle);

View File

@ -1,62 +0,0 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_QUANTIZATION_H_INCLUDED
#define DOC_QUANTIZATION_H_INCLUDED
#pragma once
#include "doc/color_histogram.h"
#include "doc/dithering_method.h"
#include "doc/frame_number.h"
#include "doc/pixel_format.h"
#include <vector>
namespace doc {
class Image;
class Palette;
class RgbMap;
class Sprite;
class Stock;
namespace quantization {
class PaletteOptimizer {
public:
void feedWithImage(Image* image);
void calculate(Palette* palette, bool has_background_layer);
private:
quantization::ColorHistogram<5, 6, 5> m_histogram;
};
void create_palette_from_images(
const std::vector<Image*>& images,
Palette* palette,
bool has_background_layer);
// Creates a new palette suitable to quantize the given RGB sprite to Indexed color.
Palette* create_palette_from_rgb(
const Sprite* sprite,
FrameNumber frameNumber,
Palette* newPalette); // Can be NULL to create a new palette
// Changes the image pixel format. The dithering method is used only
// when you want to convert from RGB to Indexed.
Image* convert_pixel_format(
const Image* src,
Image* dst, // Can be NULL to create a new image
PixelFormat pixelFormat,
DitheringMethod ditheringMethod,
const RgbMap* rgbmap,
const Palette* palette,
bool is_background);
} // namespace quantization
} // namespace doc
#endif

View File

@ -13,9 +13,9 @@
#include "base/memory.h"
#include "base/remove_from_container.h"
#include "base/unique_ptr.h"
#include "doc/doc.h"
#include "doc/image_bits.h"
#include "doc/primitives.h"
#include "doc/doc.h"
#include <cstring>
#include <vector>
@ -223,6 +223,11 @@ LayerIndex Sprite::countLayers() const
return LayerIndex(folder()->getLayersCount());
}
Layer* Sprite::layer(int layerIndex) const
{
return indexToLayer(LayerIndex(layerIndex));
}
Layer* Sprite::indexToLayer(LayerIndex index) const
{
if (index < LayerIndex(0))
@ -252,6 +257,11 @@ void Sprite::getLayersList(std::vector<Layer*>& layers) const
//////////////////////////////////////////////////////////////////////
// Palettes
Palette* Sprite::palette(frame_t frame) const
{
return getPalette(FrameNumber(frame));
}
Palette* Sprite::getPalette(FrameNumber frame) const
{
ASSERT(frame >= 0);
@ -457,28 +467,6 @@ void Sprite::remapImages(FrameNumber frameFrom, FrameNumber frameTo, const std::
//////////////////////////////////////////////////////////////////////
// Drawing
void Sprite::render(Image* image, int x, int y, FrameNumber frame) const
{
fill_rect(image, x, y, x+m_width-1, y+m_height-1,
(m_format == IMAGE_INDEXED ? transparentColor(): 0));
layer_render(folder(), image, x, y, frame);
}
int Sprite::getPixel(int x, int y, FrameNumber frame) const
{
int color = 0;
if ((x >= 0) && (y >= 0) && (x < m_width) && (y < m_height)) {
base::UniquePtr<Image> image(Image::create(m_format, 1, 1));
clear_image(image, (m_format == IMAGE_INDEXED ? transparentColor(): 0));
render(image, -x, -y, frame);
color = get_pixel(image, 0, 0);
}
return color;
}
void Sprite::pickCels(int x, int y, FrameNumber frame, int opacityThreshold, CelList& cels) const
{
std::vector<Layer*> layers;

View File

@ -77,6 +77,7 @@ namespace doc {
LayerIndex countLayers() const;
Layer* layer(int layerIndex) const;
Layer* indexToLayer(LayerIndex index) const;
LayerIndex layerToIndex(const Layer* layer) const;
@ -85,6 +86,7 @@ namespace doc {
////////////////////////////////////////
// Palettes
Palette* palette(frame_t frame) const;
Palette* getPalette(FrameNumber frame) const;
const PalettesList& getPalettes() const;
@ -124,17 +126,6 @@ namespace doc {
void remapImages(FrameNumber frameFrom, FrameNumber frameTo, const std::vector<uint8_t>& mapping);
// Draws the sprite in the given image at the given position. Before
// drawing the sprite, this function clears (with the sprite's
// background color) the rectangle area that will occupy the drawn
// sprite.
void render(Image* image, int x, int y, FrameNumber frame) const;
// Gets a pixel from the sprite in the specified position. If in the
// specified coordinates there're background this routine will
// return the 0 color (the mask-color).
int getPixel(int x, int y, FrameNumber frame) const;
void pickCels(int x, int y, FrameNumber frame, int opacityThreshold, CelList& cels) const;
private:

View File

@ -2,6 +2,7 @@
# Copyright (C) 2001-2014 David Capello
add_library(gfx-lib
clip.cpp
hsv.cpp
packing_rects.cpp
region.cpp

64
src/gfx/clip.cpp Normal file
View File

@ -0,0 +1,64 @@
// Aseprite Gfx Library
// Copyright (c) 2001-2014 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 "gfx/clip.h"
namespace gfx {
bool Clip::clip(
int avail_dst_w,
int avail_dst_h,
int avail_src_w,
int avail_src_h)
{
// Clip srcBounds
if (src.x < 0) {
size.w += src.x;
dst.x -= src.x;
src.x = 0;
}
if (src.y < 0) {
size.h += src.y;
dst.y -= src.y;
src.y = 0;
}
if (src.x + size.w > avail_src_w)
size.w -= src.x + size.w - avail_src_w;
if (src.y + size.h > avail_src_h)
size.h -= src.y + size.h - avail_src_h;
// Clip dstBounds
if (dst.x < 0) {
size.w += dst.x;
src.x -= dst.x;
dst.x = 0;
}
if (dst.y < 0) {
size.h += dst.y;
src.y -= dst.y;
dst.y = 0;
}
if (dst.x + size.w > avail_dst_w)
size.w -= dst.x + size.w - avail_dst_w;
if (dst.y + size.h > avail_dst_h)
size.h -= dst.y + size.h - avail_dst_h;
return (size.w > 0 && size.h > 0);
}
} // namespace gfx

85
src/gfx/clip.h Normal file
View File

@ -0,0 +1,85 @@
// Aseprite Gfx Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef GFX_CLIP_H_INCLUDED
#define GFX_CLIP_H_INCLUDED
#pragma once
#include "gfx/point.h"
#include "gfx/rect.h"
#include "gfx/size.h"
namespace gfx {
class Clip {
public:
Point dst;
Point src;
Size size;
Clip()
: dst(0, 0)
, src(0, 0)
, size(0, 0) {
}
Clip(int w, int h)
: dst(0, 0)
, src(0, 0)
, size(w, h) {
}
Clip(int dst_x, int dst_y, int src_x, int src_y, int w, int h)
: dst(dst_x, dst_y)
, src(src_x, src_y)
, size(w, h) {
}
Clip(int dst_x, int dst_y, const Rect& srcBounds)
: dst(dst_x, dst_y)
, src(srcBounds.x, srcBounds.y)
, size(srcBounds.w, srcBounds.h) {
}
Clip(const Point& dst, const Point& src, const Size& size)
: dst(dst)
, src(src)
, size(size) {
}
Clip(const Point& dst, const Rect& srcBounds)
: dst(dst)
, src(srcBounds.x, srcBounds.y)
, size(srcBounds.w, srcBounds.h) {
}
Clip(const Rect& bounds)
: dst(bounds.x, bounds.y)
, src(bounds.x, bounds.y)
, size(bounds.w, bounds.h) {
}
Rect dstBounds() const { return Rect(dst, size); }
Rect srcBounds() const { return Rect(src, size); }
bool operator==(const Clip& other) const {
return (dst == other.dst &&
src == other.src &&
size == other.size);
}
bool clip(
// Available area
int avail_dst_w,
int avail_dst_h,
int avail_src_w,
int avail_src_h);
};
} // namespace gfx
#endif

100
src/gfx/clip_tests.cpp Normal file
View File

@ -0,0 +1,100 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "gfx/clip.h"
using namespace gfx;
inline std::ostream& operator<<(std::ostream& os, const Clip& area)
{
return os << "("
<< area.dst.x << ", "
<< area.dst.y << ", "
<< area.src.x << ", "
<< area.src.y << ", "
<< area.size.w << ", "
<< area.size.h << ")";
}
TEST(ScaledClip, WithoutClip)
{
Clip area;
area = Clip(0, 0, 0, 0, 16, 16);
EXPECT_TRUE(area.clip(16, 16, 16, 16));
EXPECT_EQ(Clip(0, 0, 0, 0, 16, 16), area);
area = Clip(2, 2, 0, 0, 16, 16);
EXPECT_TRUE(area.clip(32, 32, 16, 16));
EXPECT_EQ(Clip(2, 2, 0, 0, 16, 16), area);
}
TEST(ScaledClip, FullyClipped)
{
Clip area;
area = Clip(32, 32, 0, 0, 16, 16);
EXPECT_FALSE(area.clip(32, 32, 16, 16));
area = Clip(-16, -16, 0, 0, 16, 16);
EXPECT_FALSE(area.clip(32, 32, 16, 16));
area = Clip(0, 0, 16, 16, 16, 16);
EXPECT_FALSE(area.clip(32, 32, 16, 16));
}
TEST(ScaledClip, WithoutZoomWithClip)
{
Clip area;
area = Clip(2, 3, 1, -1, 4, 3);
EXPECT_TRUE(area.clip(30, 29, 16, 16));
EXPECT_EQ(Clip(2, 4, 1, 0, 4, 2), area);
area = Clip(0, 0, -1, -4, 8, 5);
EXPECT_TRUE(area.clip(3, 32, 8, 8));
EXPECT_EQ(Clip(1, 4, 0, 0, 2, 1), area);
}
TEST(ScaledClip, Zoom)
{
Clip area;
area = Clip(0, 0, 0, 0, 32, 32);
EXPECT_TRUE(area.clip(32, 32, 16, 16));
EXPECT_EQ(Clip(0, 0, 0, 0, 16, 16), area);
area = Clip(0, 0, 1, 2, 32, 32);
EXPECT_TRUE(area.clip(32, 32, 32, 32));
EXPECT_EQ(Clip(0, 0, 1, 2, 31, 30), area);
// X:
// -1 0 1 2 3 4 5
// [ ]
// a[a a b] b c c c DST
// a a[a b b]b c c c SRC
//
// Y:
// -1 0 1 2 3
// [ ]
// a[a a]b b b DST
// [a a]a b b b c c SRC
area = Clip(-1, 1, 1, -1, 4, 4);
EXPECT_TRUE(area.clip(6, 4, 9, 9));
EXPECT_EQ(Clip(0, 2, 2, 0, 3, 2), area);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -4,6 +4,10 @@
// 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 "gfx/hsv.h"
#include "gfx/rgb.h"
#include <cmath>

View File

@ -4,6 +4,10 @@
// 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 <gtest/gtest.h>
#include "gfx/hsv.h"

View File

@ -4,6 +4,10 @@
// 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 <gtest/gtest.h>
#include "gfx/packing_rects.h"

View File

@ -13,7 +13,7 @@
namespace gfx {
std::ostream& operator<<(std::ostream& os, const Rect& rect)
inline std::ostream& operator<<(std::ostream& os, const Rect& rect)
{
return os << "("
<< rect.x << ", "

View File

@ -4,6 +4,10 @@
// 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 <gtest/gtest.h>
#include "gfx/border.h"

View File

@ -4,6 +4,10 @@
// 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 <gtest/gtest.h>
#include "gfx/rgb.h"

View File

@ -0,0 +1,8 @@
# Aseprite Render Library
# Copyright (C) 2001-2014 David Capello
add_library(render-lib
get_sprite_pixel.cpp
quantization.cpp
render.cpp
zoom.cpp)

20
src/render/LICENSE.txt Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2001-2014 David Capello
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

4
src/render/README.md Normal file
View File

@ -0,0 +1,4 @@
# Aseprite Render Library
*Copyright (C) 2001-2014 David Capello*
> Distributed under [MIT license](LICENSE.txt)

View File

@ -1,11 +1,11 @@
// Aseprite Document Library
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_COLOR_HISTOGRAM_H_INCLUDED
#define DOC_COLOR_HISTOGRAM_H_INCLUDED
#ifndef RENDER_COLOR_HISTOGRAM_H_INCLUDED
#define RENDER_COLOR_HISTOGRAM_H_INCLUDED
#pragma once
#include <limits>
@ -13,11 +13,12 @@
#include "doc/image.h"
#include "doc/image_traits.h"
#include "doc/median_cut.h"
#include "doc/palette.h"
namespace doc {
namespace quantization {
#include "render/median_cut.h"
namespace render {
using namespace doc;
template<int RBits, int GBits, int BBits> // Number of bits for each component in the histogram
class ColorHistogram {
@ -128,7 +129,6 @@ namespace quantization {
bool m_useHighPrecision;
};
} // namespace quantization
} // namespace doc
} // namespace render
#endif

View File

@ -0,0 +1,35 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 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 "doc/doc.h"
#include "gfx/clip.h"
#include "render/render.h"
namespace render {
using namespace doc;
color_t get_sprite_pixel(const Sprite* sprite, int x, int y, FrameNumber frame)
{
color_t color = 0;
if ((x >= 0) && (y >= 0) && (x < sprite->width()) && (y < sprite->height())) {
base::UniquePtr<Image> image(Image::create(sprite->pixelFormat(), 1, 1));
render::Render().renderSprite(image, sprite, frame,
gfx::Clip(0, 0, x, y, 1, 1));
color = get_pixel(image, 0, 0);
}
return color;
}
} // namespace render

View File

@ -0,0 +1,27 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_GET_SPRITE_PIXEL_H_INCLUDED
#define RENDER_GET_SPRITE_PIXEL_H_INCLUDED
#pragma once
#include "doc/frame_number.h"
namespace doc {
class Sprite;
}
namespace render {
using namespace doc;
// Gets a pixel from the sprite in the specified position. If in the
// specified coordinates there're background this routine will
// return the 0 color (the mask-color).
color_t get_sprite_pixel(const Sprite* sprite, int x, int y, FrameNumber frame);
} // namespace render
#endif

View File

@ -1,18 +1,17 @@
// Aseprite Document Library
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_MEDIAN_CUT_H_INCLUDED
#define DOC_MEDIAN_CUT_H_INCLUDED
#ifndef RENDER_MEDIAN_CUT_H_INCLUDED
#define RENDER_MEDIAN_CUT_H_INCLUDED
#pragma once
#include <list>
#include <queue>
namespace doc {
namespace quantization {
namespace render {
template<class Histogram>
class Box {
@ -287,7 +286,6 @@ namespace quantization {
}
}
} // namespace quantization
} // namespace doc
} // namespace render
#endif

View File

@ -1,4 +1,4 @@
// Aseprite Document Library
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
@ -8,10 +8,8 @@
#include "config.h"
#endif
#include "doc/quantization.h"
#include "render/quantization.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "doc/blend.h"
#include "doc/image.h"
#include "doc/image_bits.h"
@ -21,14 +19,17 @@
#include "doc/primitives.h"
#include "doc/rgbmap.h"
#include "doc/sprite.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"
#include "render/render.h"
#include <algorithm>
#include <limits>
#include <vector>
namespace doc {
namespace quantization {
namespace render {
using namespace doc;
using namespace gfx;
// Converts a RGB image to indexed with ordered dithering method.
@ -50,15 +51,17 @@ Palette* create_palette_from_rgb(
bool has_background_layer = (sprite->backgroundLayer() != NULL);
Image* flat_image;
ImagesCollector images(sprite->folder(), // All layers
frameNumber, // Ignored, we'll use all frames
true, // All frames,
false); // forWrite=false, read only
ImagesCollector images(
sprite->folder(), // All layers
frameNumber, // Ignored, we'll use all frames
true, // All frames,
false); // forWrite=false, read only
// Add a flat image with the current sprite's frame rendered
flat_image = Image::create(sprite->pixelFormat(), sprite->width(), sprite->height());
clear_image(flat_image, 0);
sprite->render(flat_image, 0, 0, frameNumber);
flat_image = Image::create(sprite->pixelFormat(),
sprite->width(), sprite->height());
render::Render().renderSprite(flat_image, sprite, frameNumber);
// Create an array of images
size_t nimage = images.size() + 1; // +1 for flat_image
@ -91,7 +94,7 @@ Image* convert_pixel_format(
// RGB -> Indexed with ordered dithering
if (image->pixelFormat() == IMAGE_RGB &&
pixelFormat == IMAGE_INDEXED &&
ditheringMethod == DITHERING_ORDERED) {
ditheringMethod == DitheringMethod::ORDERED) {
return ordered_dithering(image, new_image, 0, 0, rgbmap, palette);
}
@ -108,7 +111,7 @@ Image* convert_pixel_format(
// RGB -> RGB
case IMAGE_RGB:
new_image->copy(image, 0, 0, 0, 0, image->width(), image->height());
new_image->copy(image, gfx::Clip(image->bounds()));
break;
// RGB -> Grayscale
@ -189,7 +192,7 @@ Image* convert_pixel_format(
// Grayscale -> Grayscale
case IMAGE_GRAYSCALE:
new_image->copy(image, 0, 0, 0, 0, image->width(), image->height());
new_image->copy(image, gfx::Clip(image->bounds()));
break;
// Grayscale -> Indexed
@ -486,5 +489,4 @@ void create_palette_from_images(const std::vector<Image*>& images, Palette* pale
optimizer.calculate(palette, has_background_layer);
}
} // namespace quantization
} // namespace doc
} // namespace render

63
src/render/quantization.h Normal file
View File

@ -0,0 +1,63 @@
// Aseprite Rener Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_QUANTIZATION_H_INCLUDED
#define RENDER_QUANTIZATION_H_INCLUDED
#pragma once
#include "doc/dithering_method.h"
#include "doc/frame_number.h"
#include "doc/pixel_format.h"
#include "render/color_histogram.h"
#include <vector>
namespace doc {
class Image;
class Palette;
class RgbMap;
class Sprite;
class Stock;
}
namespace render {
using namespace doc;
class PaletteOptimizer {
public:
void feedWithImage(Image* image);
void calculate(Palette* palette, bool has_background_layer);
private:
ColorHistogram<5, 6, 5> m_histogram;
};
void create_palette_from_images(
const std::vector<Image*>& images,
Palette* palette,
bool has_background_layer);
// Creates a new palette suitable to quantize the given RGB sprite to Indexed color.
Palette* create_palette_from_rgb(
const Sprite* sprite,
FrameNumber frameNumber,
Palette* newPalette); // Can be NULL to create a new palette
// Changes the image pixel format. The dithering method is used only
// when you want to convert from RGB to Indexed.
Image* convert_pixel_format(
const Image* src,
Image* dst, // Can be NULL to create a new image
PixelFormat pixelFormat,
DitheringMethod ditheringMethod,
const RgbMap* rgbmap,
const Palette* palette,
bool is_background);
} // namespace render
#endif

738
src/render/render.cpp Normal file
View File

@ -0,0 +1,738 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 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 "render/render.h"
#include "doc/doc.h"
namespace render {
//////////////////////////////////////////////////////////////////////
// Scaled composite
template<class DstTraits, class SrcTraits>
class BlenderHelper
{
BLEND_COLOR m_blend_color;
color_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_color = SrcTraits::get_blender(blend_mode);
m_mask_color = src->maskColor();
}
inline void operator()(typename DstTraits::pixel_t& scanline,
const typename DstTraits::pixel_t& dst,
const typename SrcTraits::pixel_t& src,
int opacity)
{
if (src != m_mask_color)
scanline = (*m_blend_color)(dst, src, opacity);
else
scanline = dst;
}
};
template<>
class BlenderHelper<RgbTraits, GrayscaleTraits>
{
BLEND_COLOR m_blend_color;
color_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_color = RgbTraits::get_blender(blend_mode);
m_mask_color = src->maskColor();
}
inline void operator()(RgbTraits::pixel_t& scanline,
const RgbTraits::pixel_t& dst,
const GrayscaleTraits::pixel_t& src,
int opacity)
{
if (src != m_mask_color) {
int v = graya_getv(src);
scanline = (*m_blend_color)(dst, rgba(v, v, v, graya_geta(src)), opacity);
}
else
scanline = dst;
}
};
template<>
class BlenderHelper<RgbTraits, IndexedTraits>
{
const Palette* m_pal;
int m_blend_mode;
color_t m_mask_color;
public:
BlenderHelper(const Image* src, const Palette* pal, int blend_mode)
{
m_blend_mode = blend_mode;
m_mask_color = src->maskColor();
m_pal = pal;
}
inline void operator()(RgbTraits::pixel_t& scanline,
const RgbTraits::pixel_t& dst,
const IndexedTraits::pixel_t& src,
int opacity)
{
if (m_blend_mode == BLEND_MODE_COPY) {
scanline = m_pal->getEntry(src);
}
else {
if (src != m_mask_color) {
scanline = rgba_blend_normal(dst, m_pal->getEntry(src), opacity);
}
else
scanline = dst;
}
}
};
template<class DstTraits, class SrcTraits>
static void compose_scaled_image_scale_up(
Image* dst, const Image* src, const Palette* pal,
gfx::Clip area,
int opacity, int blend_mode, Zoom zoom)
{
BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blend_mode);
int px_x, px_y;
if (!area.clip(dst->width(), dst->height(),
zoom.apply(src->width()),
zoom.apply(src->height())))
return;
int px_w = zoom.apply(1);
int px_h = zoom.apply(1);
int first_px_w = px_w - (area.src.x % px_w);
int first_px_h = px_h - (area.src.y % px_h);
gfx::Rect srcBounds = zoom.remove(area.srcBounds());
gfx::Rect dstBounds = area.dstBounds();
int bottom = area.dst.y+area.size.h-1;
int line_h;
if ((area.src.x+area.size.w) % px_w > 0) ++srcBounds.w;
if ((area.src.y+area.size.h) % px_h > 0) ++srcBounds.h;
if (srcBounds.isEmpty())
return;
// the scanline variable is used to blend src/dst pixels one time for each pixel
typedef std::vector<typename DstTraits::pixel_t> Scanline;
Scanline scanline(srcBounds.w);
typename Scanline::iterator scanline_it;
#ifdef _DEBUG
typename Scanline::iterator scanline_end = scanline.end();
#endif
// Lock all necessary bits
const LockImageBits<SrcTraits> srcBits(src, srcBounds);
LockImageBits<DstTraits> dstBits(dst, dstBounds);
typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin();
#ifdef _DEBUG
typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end();
#endif
typename LockImageBits<DstTraits>::iterator dst_it, dst_end;
// For each line to draw of the source image...
dstBounds.h = 1;
for (int y=0; y<srcBounds.h; ++y) {
dst_it = dstBits.begin_area(dstBounds);
dst_end = dstBits.end_area(dstBounds);
// Read 'src' and 'dst' and blend them, put the result in `scanline'
scanline_it = scanline.begin();
for (int x=0; x<srcBounds.w; ++x) {
ASSERT(src_it >= srcBits.begin() && src_it < src_end);
ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end);
ASSERT(scanline_it >= scanline.begin() && scanline_it < scanline_end);
blender(*scanline_it, *dst_it, *src_it, opacity);
++src_it;
int delta;
if (x == 0)
delta = first_px_w;
else
delta = px_w;
while (dst_it != dst_end && delta-- > 0)
++dst_it;
++scanline_it;
}
// Get the 'height' of the line to be painted in 'dst'
if ((y == 0) && (first_px_h > 0))
line_h = first_px_h;
else
line_h = px_h;
// Draw the line in 'dst'
for (px_y=0; px_y<line_h; ++px_y) {
dst_it = dstBits.begin_area(dstBounds);
dst_end = dstBits.end_area(dstBounds);
scanline_it = scanline.begin();
int x = 0;
// first pixel
for (px_x=0; px_x<first_px_w; ++px_x) {
ASSERT(scanline_it != scanline_end);
ASSERT(dst_it != dst_end);
*dst_it = *scanline_it;
++dst_it;
if (dst_it == dst_end)
goto done_with_line;
}
++scanline_it;
++x;
// the rest of the line
for (; x<srcBounds.w; ++x) {
for (px_x=0; px_x<px_w; ++px_x) {
ASSERT(dst_it != dst_end);
*dst_it = *scanline_it;
++dst_it;
if (dst_it == dst_end)
goto done_with_line;
}
++scanline_it;
}
done_with_line:;
if (++dstBounds.y > bottom)
goto done_with_blit;
}
}
done_with_blit:;
}
template<class DstTraits, class SrcTraits>
static void compose_scaled_image_scale_down(
Image* dst, const Image* src, const Palette* pal,
gfx::Clip area,
int opacity, int blend_mode, Zoom zoom)
{
BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blend_mode);
int unbox_w = zoom.remove(1);
int unbox_h = zoom.remove(1);
if (!area.clip(dst->width(), dst->height(),
zoom.apply(src->width()),
zoom.apply(src->height())))
return;
gfx::Rect srcBounds = zoom.remove(area.srcBounds());
gfx::Rect dstBounds = area.dstBounds();
int bottom = area.dst.y+area.size.h-1;
if (srcBounds.isEmpty())
return;
// Lock all necessary bits
const LockImageBits<SrcTraits> srcBits(src, srcBounds);
LockImageBits<DstTraits> dstBits(dst, dstBounds);
typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin();
typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end();
typename LockImageBits<DstTraits>::iterator dst_it, dst_end;
// For each line to draw of the source image...
dstBounds.h = 1;
for (int y=0; y<srcBounds.h; y+=unbox_h) {
dst_it = dstBits.begin_area(dstBounds);
dst_end = dstBits.end_area(dstBounds);
for (int x=0; x<srcBounds.w; x+=unbox_w) {
ASSERT(src_it >= srcBits.begin() && src_it < src_end);
ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end);
blender(*dst_it, *dst_it, *src_it, opacity);
// Skip source pixels
for (int delta=0; delta < unbox_w && src_it != src_end; ++delta)
++src_it;
++dst_it;
}
if (++dstBounds.y > bottom)
break;
// Skip lines
for (int delta=0; delta < srcBounds.w * (unbox_h-1) && src_it != src_end; ++delta)
++src_it;
}
}
template<class DstTraits, class SrcTraits>
static void compose_scaled_image(
Image* dst, const Image* src, const Palette* pal,
const gfx::Clip& area,
int opacity, int blend_mode, Zoom zoom)
{
if (zoom.scale() >= 1.0)
compose_scaled_image_scale_up<DstTraits, SrcTraits>(dst, src, pal, area, opacity, blend_mode, zoom);
else
compose_scaled_image_scale_down<DstTraits, SrcTraits>(dst, src, pal, area, opacity, blend_mode, zoom);
}
Render::Render()
: m_sprite(NULL)
, m_currentLayer(NULL)
, m_currentFrame(0)
, m_extraCel(NULL)
, m_extraImage(NULL)
, m_bgType(BgType::TRANSPARENT)
, m_bgCheckedSize(16, 16)
, m_globalOpacity(255)
, m_onionskinType(OnionskinType::NONE)
{
}
void Render::setBgType(BgType type)
{
m_bgType = type;
}
void Render::setBgZoom(bool state)
{
m_bgZoom = state;
}
void Render::setBgColor1(color_t color)
{
m_bgColor1 = color;
}
void Render::setBgColor2(color_t color)
{
m_bgColor2 = color;
}
void Render::setBgCheckedSize(const gfx::Size& size)
{
m_bgCheckedSize = size;
}
void Render::setPreviewImage(const Layer* layer, FrameNumber frame, Image* image)
{
m_selectedLayer = layer;
m_selectedFrame = frame;
m_previewImage = image;
}
void Render::setExtraImage(
const Cel* cel, const Image* image, int blendMode,
const Layer* currentLayer,
FrameNumber currentFrame)
{
m_extraCel = cel;
m_extraImage = image;
m_extraBlendMode = blendMode;
m_currentLayer = currentLayer;
m_currentFrame = currentFrame;
}
void Render::removePreviewImage()
{
m_previewImage = NULL;
}
void Render::removeExtraImage()
{
m_extraCel = NULL;
}
void Render::setOnionskin(OnionskinType type, int prevs, int nexts, int opacityBase, int opacityStep)
{
m_onionskinType = type;
m_onionskinPrevs = prevs;
m_onionskinNexts = nexts;
m_onionskinOpacityBase = opacityBase;
m_onionskinOpacityStep = opacityStep;
}
void Render::disableOnionskin()
{
m_onionskinType = OnionskinType::NONE;
}
void Render::renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame)
{
renderSprite(dstImage, sprite, frame,
gfx::Clip(sprite->bounds()), Zoom(1, 1));
}
void Render::renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame,
const gfx::Clip& area)
{
renderSprite(dstImage, sprite, frame, area, Zoom(1, 1));
}
void Render::renderLayer(
Image* dstImage,
const Layer* layer,
FrameNumber frame)
{
renderLayer(dstImage, layer, frame,
gfx::Clip(layer->sprite()->bounds()));
}
void Render::renderLayer(
Image* dstImage,
const Layer* layer,
FrameNumber frame,
const gfx::Clip& area)
{
m_sprite = layer->sprite();
RenderScaledImage scaled_func =
getRenderScaledImageFunc(
dstImage->pixelFormat(),
m_sprite->pixelFormat());
if (!scaled_func)
return;
const LayerImage* background = m_sprite->backgroundLayer();
bool need_checked_bg = (background != NULL ? !background->isVisible(): true);
color_t bg_color = 0;
m_globalOpacity = 255;
renderLayer(layer, dstImage, area,
frame, Zoom(1, 1), scaled_func,
true, true, -1);
}
void Render::renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame,
const gfx::Clip& area,
Zoom zoom)
{
m_sprite = sprite;
RenderScaledImage scaled_func =
getRenderScaledImageFunc(
dstImage->pixelFormat(),
m_sprite->pixelFormat());
if (!scaled_func)
return;
const LayerImage* bgLayer = m_sprite->backgroundLayer();
color_t bg_color = 0;
if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
switch (dstImage->pixelFormat()) {
case IMAGE_RGB:
case IMAGE_GRAYSCALE:
if (bgLayer && bgLayer->isVisible())
bg_color = m_sprite->getPalette(frame)->getEntry(m_sprite->transparentColor());
break;
case IMAGE_INDEXED:
bg_color = m_sprite->transparentColor();
break;
}
}
// Draw checked background
switch (m_bgType) {
case BgType::CHECKED:
if (bgLayer && bgLayer->isVisible())
fill_rect(dstImage, area.dstBounds(), bg_color);
else
renderBackground(dstImage, area, zoom);
break;
case BgType::TRANSPARENT:
fill_rect(dstImage, area.dstBounds(), bg_color);
break;
}
// Draw the current frame.
m_globalOpacity = 255;
renderLayer(
m_sprite->folder(), dstImage,
area, frame, zoom, scaled_func,
true, true, -1);
// Onion-skin feature: Draw previous/next frames with different
// opacity (<255)
if (m_onionskinType != OnionskinType::NONE) {
for (FrameNumber f=frame.previous(m_onionskinPrevs);
f <= frame.next(m_onionskinNexts); ++f) {
if (f == frame || f < 0 || f > m_sprite->lastFrame())
continue;
else if (f < frame)
m_globalOpacity = m_onionskinOpacityBase - m_onionskinOpacityStep * ((frame - f)-1);
else
m_globalOpacity = m_onionskinOpacityBase - m_onionskinOpacityStep * ((f - frame)-1);
if (m_globalOpacity > 0) {
m_globalOpacity = MID(0, m_globalOpacity, 255);
int blend_mode = -1;
if (m_onionskinType == OnionskinType::MERGE)
blend_mode = BLEND_MODE_NORMAL;
else if (m_onionskinType == OnionskinType::RED_BLUE_TINT)
blend_mode = (f < frame ? BLEND_MODE_RED_TINT: BLEND_MODE_BLUE_TINT);
renderLayer(m_sprite->folder(), dstImage,
area, f, zoom, scaled_func,
true, true, blend_mode);
}
}
}
}
void Render::renderBackground(Image* image,
const gfx::Clip& area,
Zoom zoom)
{
int x, y, u, v;
int tile_w = m_bgCheckedSize.w;
int tile_h = m_bgCheckedSize.h;
if (m_bgZoom) {
tile_w = zoom.apply(tile_w);
tile_h = zoom.apply(tile_h);
}
// Tile size
if (tile_w < zoom.apply(1)) tile_w = zoom.apply(1);
if (tile_h < zoom.apply(1)) tile_h = zoom.apply(1);
if (tile_w < 1) tile_w = 1;
if (tile_h < 1) tile_h = 1;
// Tile position (u,v) is the number of tile we start in "area.src" coordinate
u = (area.src.x / tile_w);
v = (area.src.y / tile_h);
// Position where we start drawing the first tile in "image"
int x_start = -(area.src.x % tile_w);
int y_start = -(area.src.y % tile_h);
gfx::Rect dstBounds = area.dstBounds();
// Draw checked background (tile by tile)
int u_start = u;
for (y=y_start-tile_h; y<image->height()+tile_h; y+=tile_h) {
for (x=x_start-tile_w; x<image->width()+tile_w; x+=tile_w) {
gfx::Rect fillRc = dstBounds.createIntersect(gfx::Rect(x, y, tile_w, tile_h));
if (!fillRc.isEmpty())
fill_rect(
image, fillRc.x, fillRc.y, fillRc.x+fillRc.w-1, fillRc.y+fillRc.h-1,
(((u+v))&1)? m_bgColor2: m_bgColor1);
++u;
}
u = u_start;
++v;
}
}
void Render::renderImage(Image* dst_image, const Image* src_image,
const Palette* pal, int x, int y, Zoom zoom, int opacity, int blend_mode)
{
RenderScaledImage scaled_func = getRenderScaledImageFunc(
dst_image->pixelFormat(),
src_image->pixelFormat());
if (!scaled_func)
return;
scaled_func(dst_image, src_image, pal,
gfx::Clip(x, y, 0, 0,
zoom.apply(src_image->width()),
zoom.apply(src_image->height())),
opacity, blend_mode, zoom);
}
void Render::renderLayer(
const Layer* layer,
Image *image,
const gfx::Clip& area,
FrameNumber frame, Zoom zoom,
RenderScaledImage scaled_func,
bool render_background,
bool render_transparent,
int blend_mode)
{
// we can't read from this layer
if (!layer->isVisible())
return;
switch (layer->type()) {
case ObjectType::LayerImage: {
if ((!render_background && layer->isBackground()) ||
(!render_transparent && !layer->isBackground()))
break;
const Cel* cel = static_cast<const LayerImage*>(layer)->getCel(frame);
if (cel != NULL) {
Image* src_image;
// Is the 'm_previewImage' set to be used with this layer?
if ((m_previewImage) &&
(m_selectedLayer == layer) &&
(m_selectedFrame == frame)) {
src_image = m_previewImage;
}
// If not, we use the original cel-image from the images' stock
else {
src_image = cel->image();
}
if (src_image) {
int t, output_opacity;
output_opacity = MID(0, cel->opacity(), 255);
output_opacity = INT_MULT(output_opacity, m_globalOpacity, t);
ASSERT(src_image->maskColor() == m_sprite->transparentColor());
renderCel(image, src_image,
m_sprite->getPalette(frame),
cel, area, scaled_func,
output_opacity,
(blend_mode < 0 ?
static_cast<const LayerImage*>(layer)->getBlendMode():
blend_mode),
zoom);
}
}
break;
}
case ObjectType::LayerFolder: {
LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin();
LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
for (; it != end; ++it) {
renderLayer(*it, image,
area, frame, zoom, scaled_func,
render_background,
render_transparent,
blend_mode);
}
break;
}
}
// Draw extras
if (m_extraCel &&
m_extraImage &&
layer == m_currentLayer &&
frame == m_currentFrame) {
if (m_extraCel->opacity() > 0) {
renderCel(image, m_extraImage,
m_sprite->getPalette(frame),
m_extraCel, area, scaled_func,
m_extraCel->opacity(),
m_extraBlendMode, zoom);
}
}
}
void Render::renderCel(
Image* dst_image,
const Image* cel_image,
const Palette* pal,
const Cel* cel,
const gfx::Clip& area,
RenderScaledImage scaled_func,
int opacity, int blend_mode, Zoom zoom)
{
int cel_x = zoom.apply(cel->x());
int cel_y = zoom.apply(cel->y());
gfx::Rect src_bounds =
area.srcBounds().createIntersect(
gfx::Rect(
cel_x, cel_y,
zoom.apply(cel_image->width()),
zoom.apply(cel_image->height())));
if (src_bounds.isEmpty())
return;
(*scaled_func)(dst_image, cel_image, pal,
gfx::Clip(
area.dst.x + src_bounds.x - area.src.x,
area.dst.y + src_bounds.y - area.src.y,
src_bounds.x - cel_x,
src_bounds.y - cel_y,
src_bounds.w,
src_bounds.h),
opacity, blend_mode, zoom);
}
// static
Render::RenderScaledImage Render::getRenderScaledImageFunc(
PixelFormat dstFormat,
PixelFormat srcFormat)
{
switch (srcFormat) {
case IMAGE_RGB:
switch (dstFormat) {
case IMAGE_RGB: return compose_scaled_image<RgbTraits, RgbTraits>;
case IMAGE_GRAYSCALE: return compose_scaled_image<GrayscaleTraits, RgbTraits>;
case IMAGE_INDEXED: return compose_scaled_image<IndexedTraits, RgbTraits>;
}
break;
case IMAGE_GRAYSCALE:
switch (dstFormat) {
case IMAGE_RGB: return compose_scaled_image<RgbTraits, GrayscaleTraits>;
case IMAGE_GRAYSCALE: return compose_scaled_image<GrayscaleTraits, GrayscaleTraits>;
case IMAGE_INDEXED: return compose_scaled_image<IndexedTraits, GrayscaleTraits>;
}
break;
case IMAGE_INDEXED:
switch (dstFormat) {
case IMAGE_RGB: return compose_scaled_image<RgbTraits, IndexedTraits>;
case IMAGE_GRAYSCALE: return compose_scaled_image<GrayscaleTraits, IndexedTraits>;
case IMAGE_INDEXED: return compose_scaled_image<IndexedTraits, IndexedTraits>;
}
break;
}
ASSERT(false && "Invalid pixel formats");
return NULL;
}
void composite_image(Image* dst, const Image* src,
int x, int y, int opacity, int blend_mode)
{
// As the background is not rendered in renderImage(), we don't need
// to configure the Render instance's BgType.
Render().renderImage(
dst, src, NULL, x, y, Zoom(1, 1),
opacity, blend_mode);
}
} // namespace render

172
src/render/render.h Normal file
View File

@ -0,0 +1,172 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef RENDER_RENDER_H_INCLUDED
#define RENDER_RENDER_H_INCLUDED
#pragma once
#include "doc/color.h"
#include "doc/frame_number.h"
#include "doc/pixel_format.h"
#include "gfx/fwd.h"
#include "gfx/size.h"
#include "render/zoom.h"
namespace gfx {
class Clip;
}
namespace doc {
class Cel;
class Image;
class Layer;
class Palette;
class Sprite;
}
namespace render {
using namespace doc;
enum class BgType {
NONE,
TRANSPARENT,
CHECKED,
};
enum class OnionskinType {
NONE,
MERGE,
RED_BLUE_TINT,
};
class Render {
public:
Render();
// Background configuration
void setBgType(BgType type);
void setBgZoom(bool state);
void setBgColor1(color_t color);
void setBgColor2(color_t color);
void setBgCheckedSize(const gfx::Size& size);
// Sets the preview image. This preview image is an alternative
// image to be used for the given layer/frame.
void setPreviewImage(const Layer* layer, FrameNumber frame, Image* drawable);
void removePreviewImage();
// Sets an extra cel/image to be drawn after the current
// layer/frame.
void setExtraImage(
const Cel* cel, const Image* image, int blendMode,
const Layer* currentLayer,
FrameNumber currentFrame);
void removeExtraImage();
void setOnionskin(OnionskinType type,
int prevs, int nexts, int opacityBase, int opacityStep);
void disableOnionskin();
void renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame);
void renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame,
const gfx::Clip& area);
void renderLayer(
Image* dstImage,
const Layer* layer,
FrameNumber frame);
void renderLayer(
Image* dstImage,
const Layer* layer,
FrameNumber frame,
const gfx::Clip& area);
// Main function used to render the sprite. Draws the given sprite
// frame in a new image and return it. Note: zoomedRect must have
// the zoom applied (zoomedRect = zoom.apply(spriteRect)).
void renderSprite(
Image* dstImage,
const Sprite* sprite,
FrameNumber frame,
const gfx::Clip& area,
Zoom zoom);
// Extra functions
void renderBackground(Image* image,
const gfx::Clip& area,
Zoom zoom);
void renderImage(Image* dst_image, const Image* src_image,
const Palette* pal, int x, int y, Zoom zoom,
int opacity, int blend_mode);
private:
typedef void (*RenderScaledImage)(
Image* dst, const Image* src, const Palette* pal,
const gfx::Clip& area,
int opacity, int blend_mode, Zoom zoom);
void renderLayer(
const Layer* layer,
Image* image,
const gfx::Clip& area,
FrameNumber frame, Zoom zoom,
RenderScaledImage renderScaledImage,
bool render_background,
bool render_transparent,
int blend_mode);
void renderCel(
Image* dst_image,
const Image* cel_image,
const Palette* pal,
const Cel* cel,
const gfx::Clip& area,
RenderScaledImage scaled_func,
int opacity, int blend_mode, Zoom zoom);
static RenderScaledImage getRenderScaledImageFunc(
PixelFormat dstFormat,
PixelFormat srcFormat);
const Sprite* m_sprite;
const Layer* m_currentLayer;
FrameNumber m_currentFrame;
const Cel* m_extraCel;
const Image* m_extraImage;
int m_extraBlendMode;
BgType m_bgType;
bool m_bgZoom;
color_t m_bgColor1;
color_t m_bgColor2;
gfx::Size m_bgCheckedSize;
int m_globalOpacity;
const Layer* m_selectedLayer;
FrameNumber m_selectedFrame;
Image* m_previewImage;
OnionskinType m_onionskinType;
int m_onionskinPrevs;
int m_onionskinNexts;
int m_onionskinOpacityBase;
int m_onionskinOpacityStep;
};
void composite_image(Image* dst, const Image* src,
int x, int y, int opacity, int blend_mode);
} // namespace render
#endif

221
src/render/render_tests.cpp Normal file
View File

@ -0,0 +1,221 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "render/render.h"
#include "base/unique_ptr.h"
#include "doc/cel.h"
#include "doc/context.h"
#include "doc/document.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/primitives.h"
using namespace doc;
using namespace render;
template<typename T>
class RenderAllModes : public testing::Test {
protected:
RenderAllModes() { }
};
typedef testing::Types<RgbTraits, GrayscaleTraits, IndexedTraits> ImageAllTraits;
TYPED_TEST_CASE(RenderAllModes, ImageAllTraits);
// a b
// c d
#define EXPECT_2X2_PIXELS(image, a, b, c, d) \
EXPECT_EQ(a, get_pixel(image, 0, 0)); \
EXPECT_EQ(b, get_pixel(image, 1, 0)); \
EXPECT_EQ(c, get_pixel(image, 0, 1)); \
EXPECT_EQ(d, get_pixel(image, 1, 1))
// a b c d
// e f g h
// i j k l
// m n o p
#define EXPECT_4X4_PIXELS(image, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
EXPECT_EQ(a, get_pixel(image, 0, 0)); \
EXPECT_EQ(b, get_pixel(image, 1, 0)); \
EXPECT_EQ(c, get_pixel(image, 2, 0)); \
EXPECT_EQ(d, get_pixel(image, 3, 0)); \
EXPECT_EQ(e, get_pixel(image, 0, 1)); \
EXPECT_EQ(f, get_pixel(image, 1, 1)); \
EXPECT_EQ(g, get_pixel(image, 2, 1)); \
EXPECT_EQ(h, get_pixel(image, 3, 1)); \
EXPECT_EQ(i, get_pixel(image, 0, 2)); \
EXPECT_EQ(j, get_pixel(image, 1, 2)); \
EXPECT_EQ(k, get_pixel(image, 2, 2)); \
EXPECT_EQ(l, get_pixel(image, 3, 2)); \
EXPECT_EQ(m, get_pixel(image, 0, 3)); \
EXPECT_EQ(n, get_pixel(image, 1, 3)); \
EXPECT_EQ(o, get_pixel(image, 2, 3)); \
EXPECT_EQ(p, get_pixel(image, 3, 3))
TEST(Render, Basic)
{
Context ctx;
Document* doc = ctx.documents().add(2, 2, ColorMode::RGB);
Image* src = doc->sprite()->layer(0)->cel(0)->image();
clear_image(src, 2);
base::UniquePtr<Image> dst(Image::create(IMAGE_RGB, 2, 2));
clear_image(dst, 1);
EXPECT_2X2_PIXELS(dst, 1, 1, 1, 1);
Render render;
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
EXPECT_2X2_PIXELS(dst, 2, 2, 2, 2);
}
TYPED_TEST(RenderAllModes, CheckDefaultBackgroundMode)
{
typedef TypeParam ImageTraits;
Context ctx;
Document* doc = ctx.documents().add(2, 2,
ColorMode(ImageTraits::pixel_format));
EXPECT_TRUE(!doc->sprite()->layer(0)->isBackground());
Image* src = doc->sprite()->layer(0)->cel(0)->image();
clear_image(src, 0);
put_pixel(src, 1, 1, 1);
base::UniquePtr<Image> dst(Image::create(ImageTraits::pixel_format, 2, 2));
clear_image(dst, 1);
EXPECT_2X2_PIXELS(dst, 1, 1, 1, 1);
Render render;
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
// Default background mode is to set all pixels to transparent color
EXPECT_2X2_PIXELS(dst, 0, 0, 0, 1);
}
TEST(Render, DefaultBackgroundModeWithNonzeroTransparentIndex)
{
Context ctx;
Document* doc = ctx.documents().add(2, 2, ColorMode::INDEXED);
doc->sprite()->setTransparentColor(2); // Transparent color is index 2
EXPECT_TRUE(!doc->sprite()->layer(0)->isBackground());
Image* src = doc->sprite()->layer(0)->cel(0)->image();
clear_image(src, 2);
put_pixel(src, 1, 1, 1);
base::UniquePtr<Image> dst(Image::create(IMAGE_INDEXED, 2, 2));
clear_image(dst, 1);
EXPECT_2X2_PIXELS(dst, 1, 1, 1, 1);
Render render;
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
EXPECT_2X2_PIXELS(dst, 2, 2, 2, 1); // Indexed transparent
dst.reset(Image::create(IMAGE_RGB, 2, 2));
clear_image(dst, 1);
EXPECT_2X2_PIXELS(dst, 1, 1, 1, 1);
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
color_t c1 = doc->sprite()->palette(0)->entry(1);
EXPECT_NE(0, c1);
EXPECT_2X2_PIXELS(dst, 0, 0, 0, c1); // RGB transparent
}
TEST(Render, CheckedBackground)
{
Context ctx;
Document* doc = ctx.documents().add(4, 4, ColorMode::RGB);
base::UniquePtr<Image> dst(Image::create(IMAGE_RGB, 4, 4));
clear_image(dst, 0);
Render render;
render.setBgType(BgType::CHECKED);
render.setBgZoom(true);
render.setBgColor1(1);
render.setBgColor2(2);
render.setBgCheckedSize(gfx::Size(1, 1));
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
EXPECT_4X4_PIXELS(dst,
1, 2, 1, 2,
2, 1, 2, 1,
1, 2, 1, 2,
2, 1, 2, 1);
render.setBgCheckedSize(gfx::Size(2, 2));
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
EXPECT_4X4_PIXELS(dst,
1, 1, 2, 2,
1, 1, 2, 2,
2, 2, 1, 1,
2, 2, 1, 1);
render.setBgCheckedSize(gfx::Size(3, 3));
render.renderSprite(dst, doc->sprite(), FrameNumber(0));
EXPECT_4X4_PIXELS(dst,
1, 1, 1, 2,
1, 1, 1, 2,
1, 1, 1, 2,
2, 2, 2, 1);
render.setBgCheckedSize(gfx::Size(1, 1));
render.renderSprite(dst,
doc->sprite(), FrameNumber(0),
gfx::Clip(dst->bounds()),
Zoom(2, 1));
EXPECT_4X4_PIXELS(dst,
1, 1, 2, 2,
1, 1, 2, 2,
2, 2, 1, 1,
2, 2, 1, 1);
}
TEST(Render, ZoomAndDstBounds)
{
Context ctx;
// Create this image:
// 0 0 0
// 0 4 4
// 0 4 4
Document* doc = ctx.documents().add(3, 3, ColorMode::RGB);
Image* src = doc->sprite()->layer(0)->cel(0)->image();
clear_image(src, 0);
fill_rect(src, 1, 1, 2, 2, 4);
base::UniquePtr<Image> dst(Image::create(IMAGE_RGB, 4, 4));
clear_image(dst, 0);
Render render;
render.setBgType(BgType::CHECKED);
render.setBgZoom(true);
render.setBgColor1(1);
render.setBgColor2(2);
render.setBgCheckedSize(gfx::Size(1, 1));
render.renderSprite(dst, doc->sprite(), FrameNumber(0),
gfx::Clip(1, 1, 0, 0, 2, 2),
Zoom(1, 1));
EXPECT_4X4_PIXELS(dst,
0, 0, 0, 0,
0, 1, 2, 0,
0, 2, 4, 0,
0, 0, 0, 0);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

35
src/render/zoom.cpp Normal file
View File

@ -0,0 +1,35 @@
// Aseprite Render Library
// Copyright (c) 2001-2014 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 "render/zoom.h"
namespace render {
void Zoom::in()
{
if (m_den > 1) {
m_den--;
}
else if (m_num < 64) {
m_num++;
}
}
void Zoom::out()
{
if (m_num > 1) {
m_num--;
}
else if (m_den < 32) {
m_den++;
}
}
} // namespace render

View File

@ -1,28 +1,16 @@
/* Aseprite
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
// Aseprite Render Library
// Copyright (c) 2001-2014 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef APP_ZOOM_H_INCLUDED
#define APP_ZOOM_H_INCLUDED
#ifndef RENDER_ZOOM_H_INCLUDED
#define RENDER_ZOOM_H_INCLUDED
#pragma once
#include "gfx/rect.h"
namespace app {
namespace render {
class Zoom {
public:
@ -67,6 +55,6 @@ namespace app {
int m_den;
};
} // namespace app
} // namespace render
#endif // APP_ZOOM_H_INCLUDED
#endif // RENDER_ZOOM_H_INCLUDED