New render engine to use bicubic interpolation when zoom < %100 (fix #1671)

This only works with Skia back-end.
This commit is contained in:
David Capello 2018-06-22 15:41:22 -03:00
parent e06735a4c0
commit 511752fea7
13 changed files with 181 additions and 63 deletions

View File

@ -152,6 +152,7 @@
<option id="mini_font" type="std::string" migrate="Options.UserMiniFont" />
</section>
<section id="experimental" text="Experimental">
<option id="new_render_engine" type="bool" default="true" />
<option id="use_native_clipboard" type="bool" default="true" />
<option id="use_native_file_dialog" type="bool" default="false" />
<option id="one_finger_as_mouse_movement" type="bool" default="true" />

View File

@ -1012,6 +1012,7 @@ disable_extension = &Disable
uninstall_extension = &Uninstall
open_extension_folder = Open &Folder
user_interface = User Interface
new_render_engine = New render engine for sprite editor
native_clipboard = Use native clipboard
native_file_dialog = Use native file dialog
one_finger_as_mouse_movement = Interpret one finger as mouse movement

View File

@ -361,6 +361,11 @@
<!-- Experimental -->
<vbox id="section_experimental">
<separator text="@.user_interface" horizontal="true" />
<hbox>
<check text="@.new_render_engine"
pref="experimental.new_render_engine" />
<link text="(#1671)" url="https://github.com/aseprite/aseprite/issues/1671" />
</hbox>
<check id="native_clipboard" text="@.native_clipboard" />
<check id="native_file_dialog" text="@.native_file_dialog" />
<check id="one_finger_as_mouse_movement"

View File

@ -60,14 +60,17 @@ because they don't depend on any other component.
# Debugging Tricks
When Aseprite is compiled with `ENABLE_DEVMODE`, you have the
following extra commands available:
following extra commands/features available:
* `F5`: On Windows shows the amount of used memory.
* `F1`: Switch/test Screen/UI Scaling values.
* `F1`: Switch new/old render engine.
* `Ctrl+F1`: Switch/test Screen/UI Scaling values.
* `Ctrl+Alt+Shift+Q`: crashes the application in case that you want to
test the anticrash feature or your need a memory dump file.
* `Ctrl+Alt+Shift+R`: recover the active document from the data
recovery store.
* `aseprite.ini`: `[perf] show_render_time=true` shows a performance
clock in the Editor.
# Detect Platform

View File

@ -478,7 +478,8 @@ bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg)
}
// F1 switches screen/UI scaling
if (msg->scancode() == kKeyF1) {
if (msg->ctrlPressed() &&
msg->scancode() == kKeyF1) {
try {
she::Display* display = getDisplay();
int screenScale = display->scale();

View File

@ -61,6 +61,7 @@
#include "she/system.h"
#include "ui/ui.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
@ -310,6 +311,14 @@ void Editor::getInvalidDecoratoredRegion(gfx::Region& region)
// changes (e.g. symmetry handles).
if ((m_flags & kShowDecorators) && m_decorator)
m_decorator->getInvalidDecoratoredRegion(this, region);
#if ENABLE_DEVMODE
// TODO put this in other widget
if (Preferences::instance().perf.showRenderTime()) {
if (!m_perfInfoBounds.isEmpty())
region |= gfx::Region(m_perfInfoBounds);
}
#endif // ENABLE_DEVMODE
}
void Editor::setLayer(const Layer* layer)
@ -488,62 +497,87 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
gfx::Rect rc = m_sprite->bounds().createIntersection(spriteRectToDraw);
rc = m_proj.apply(rc);
int dest_x = dx + m_padding.x + rc.x;
int dest_y = dy + m_padding.y + rc.y;
gfx::Rect dest(dx + m_padding.x + rc.x,
dy + m_padding.y + rc.y, 0, 0);
// Clip from graphics/screen
const gfx::Rect& clip = g->getClipBounds();
if (dest_x < clip.x) {
rc.x += clip.x - dest_x;
rc.w -= clip.x - dest_x;
dest_x = clip.x;
if (dest.x < clip.x) {
rc.x += clip.x - dest.x;
rc.w -= clip.x - dest.x;
dest.x = clip.x;
}
if (dest_y < clip.y) {
rc.y += clip.y - dest_y;
rc.h -= clip.y - dest_y;
dest_y = clip.y;
if (dest.y < clip.y) {
rc.y += clip.y - dest.y;
rc.h -= clip.y - dest.y;
dest.y = clip.y;
}
if (dest_x+rc.w > clip.x+clip.w) {
rc.w = clip.x+clip.w-dest_x;
if (dest.x+rc.w > clip.x+clip.w) {
rc.w = clip.x+clip.w-dest.x;
}
if (dest_y+rc.h > clip.y+clip.h) {
rc.h = clip.y+clip.h-dest_y;
if (dest.y+rc.h > clip.y+clip.h) {
rc.h = clip.y+clip.h-dest.y;
}
if (rc.isEmpty())
return;
base::UniquePtr<Image> rendered(NULL);
// Bounds of pixels from the sprite canvas that will be exposed in
// this render cycle.
gfx::Rect expose = m_proj.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_proj.scaleX() < 1.0)
expose.enlargeXW(int(1./m_proj.scaleX()));
// If the zoom level is more than %100 we add an extra pixel to
// expose just in case the zoom requires to display it. Note:
// this is really necessary to avoid showing invalid destination
// areas in ToolLoopImpl.
else if (m_proj.scaleX() > 1.0)
expose.enlargeXW(1);
if (m_proj.scaleY() < 1.0)
expose.enlargeYH(int(1./m_proj.scaleY()));
else if (m_proj.scaleY() > 1.0)
expose.enlargeYH(1);
expose &= m_sprite->bounds();
const int maxw = std::max(0, m_sprite->width()-expose.x);
const int maxh = std::max(0, m_sprite->height()-expose.y);
expose.w = MID(0, expose.w, maxw);
expose.h = MID(0, expose.h, maxh);
if (expose.isEmpty())
return;
// rc2 is the rectangle used to create a temporal rendered image of the sprite
const bool newEngine = Preferences::instance().experimental.newRenderEngine();
gfx::Rect rc2;
if (newEngine) {
rc2 = expose; // New engine, exposed rectangle (without zoom)
dest.x = dx + m_padding.x + m_proj.applyX(rc2.x);
dest.y = dy + m_padding.y + m_proj.applyY(rc2.y);
dest.w = m_proj.applyX(rc2.w);
dest.h = m_proj.applyY(rc2.h);
}
else {
rc2 = rc; // Old engine, same rectangle with zoom
dest.w = rc.w;
dest.h = rc.h;
}
base::UniquePtr<Image> rendered(nullptr);
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_proj.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_proj.scaleX() < 1.0)
expose.enlargeXW(int(1./m_proj.scaleX()));
// If the zoom level is more than %100 we add an extra pixel to
// expose just in case the zoom requires to display it. Note:
// this is really necessary to avoid showing invalid destination
// areas in ToolLoopImpl.
else if (m_proj.scaleX() > 1.0)
expose.enlargeXW(1);
if (m_proj.scaleY() < 1.0)
expose.enlargeYH(int(1./m_proj.scaleY()));
else if (m_proj.scaleY() > 1.0)
expose.enlargeYH(1);
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
}
m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose));
// Create a temporary RGB bitmap to draw all to it
rendered.reset(Image::create(IMAGE_RGB, rc.w, rc.h,
rendered.reset(Image::create(IMAGE_RGB, rc2.w, rc2.h,
m_renderEngine->getRenderImageBuffer()));
m_renderEngine->setRefLayersVisiblity(true);
@ -552,7 +586,8 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
m_renderEngine->setNonactiveLayersOpacity(Preferences::instance().experimental.nonactiveLayersOpacity());
else
m_renderEngine->setNonactiveLayersOpacity(255);
m_renderEngine->setProjection(m_proj);
m_renderEngine->setProjection(
newEngine ? render::Projection(): m_proj);
m_renderEngine->setupBackground(m_document, rendered->pixelFormat());
m_renderEngine->disableOnionskin();
@ -592,7 +627,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
}
m_renderEngine->renderSprite(
rendered, m_sprite, m_frame, gfx::Clip(0, 0, rc));
rendered, m_sprite, m_frame, gfx::Clip(0, 0, rc2));
m_renderEngine->removeExtraImage();
}
@ -602,23 +637,27 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
if (rendered) {
// Convert the render to a she::Surface
static she::Surface* tmp;
if (!tmp || tmp->width() < rc.w || tmp->height() < rc.h) {
static she::Surface* tmp = nullptr; // TODO move this to other centralized place
if (!tmp || tmp->width() < rc2.w || tmp->height() < rc2.h) {
const int maxw = std::max(rc2.w, tmp ? tmp->width(): 0);
const int maxh = std::max(rc2.h, tmp ? tmp->height(): 0);
if (tmp)
tmp->dispose();
tmp = she::instance()->createSurface(rc.w, rc.h);
tmp = she::instance()->createSurface(maxw, maxh);
}
if (tmp->nativeHandle()) {
if (newEngine)
tmp->clear(); // TODO why we need this?
convert_image_to_surface(rendered, m_sprite->palette(m_frame),
tmp, 0, 0, 0, 0, rc.w, rc.h);
g->blit(tmp, 0, 0, dest_x, dest_y, rc.w, rc.h);
m_brushPreview.invalidateRegion(
gfx::Region(
gfx::Rect(dest_x, dest_y, rc.w, rc.h)));
tmp, 0, 0, 0, 0, rc2.w, rc2.h);
if (newEngine) {
g->drawRgbaSurface(tmp, gfx::Rect(0, 0, rc2.w, rc2.h), dest);
}
else {
g->blit(tmp, 0, 0, dest.x, dest.y, dest.w, dest.h);
}
m_brushPreview.invalidateRegion(gfx::Region(dest));
}
}
@ -630,7 +669,7 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& sprite
m_proj.applyX(m_sprite->width()),
m_proj.applyY(m_sprite->height()));
IntersectClip clip(g, gfx::Rect(dest_x, dest_y, rc.w, rc.h));
IntersectClip clip(g, dest);
if (clip) {
// Draw the pixel grid
if ((m_proj.zoom().scale() > 2.0) && m_docPref.show.pixelGrid()) {
@ -1666,6 +1705,16 @@ bool Editor::onProcessMessage(Message* msg)
break;
case kKeyDownMessage:
#if ENABLE_DEVMODE
// Switch render mode
if (!msg->ctrlPressed() &&
static_cast<KeyMessage*>(msg)->scancode() == kKeyF1) {
Preferences::instance().experimental.newRenderEngine(
!Preferences::instance().experimental.newRenderEngine());
invalidate();
return true;
}
#endif
if (m_sprite) {
EditorStatePtr holdState(m_state);
bool used = m_state->onKeyDown(this, static_cast<KeyMessage*>(msg));
@ -1778,18 +1827,25 @@ void Editor::onPaint(ui::PaintEvent& ev)
drawSpriteUnclippedRect(g, gfx::Rect(0, 0, m_sprite->width(), m_sprite->height()));
renderElapsed = renderChrono.elapsed();
#if ENABLE_DEVMODE
// Show performance stats (TODO show performance stats in other widget)
if (Preferences::instance().perf.showRenderTime()) {
View* view = View::getView(this);
gfx::Rect vp = view->viewportBounds();
char buf[128];
sprintf(buf, "%.3f", renderElapsed);
sprintf(buf, "%c %.4gs",
Preferences::instance().experimental.newRenderEngine() ? 'N': 'O',
renderElapsed);
g->drawText(
buf,
gfx::rgba(255, 255, 255, 255),
gfx::rgba(0, 0, 0, 255),
vp.origin() - bounds().origin());
m_perfInfoBounds.setOrigin(vp.origin());
m_perfInfoBounds.setSize(g->measureUIText(buf));
}
#endif // ENABLE_DEVMODE
// Draw the mask boundaries
if (m_document->getMaskBoundaries()) {

View File

@ -405,6 +405,10 @@ namespace app {
// TODO could we avoid one extra field just to do this?
gfx::Point m_oldMainTilePos;
#if ENABLE_DEVMODE
gfx::Rect m_perfInfoBounds;
#endif
// The render engine must be shared between all editors so when a
// DrawingState is being used in one editor, other editors for the
// same document can show the same preview image/stroke being drawn

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2017 David Capello
// Copyright (C) 2012-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -420,4 +420,16 @@ void Alleg4Surface::drawRgbaSurface(const Surface* src, int srcx, int srcy, int
destroy_bitmap(tmp);
}
void Alleg4Surface::drawRgbaSurface(const Surface* src, const gfx::Rect& srcRect, const gfx::Rect& dstRect)
{
ASSERT(src);
ASSERT(static_cast<Alleg4Surface*>(src)->m_bmp);
ASSERT(static_cast<Alleg4Surface*>(this)->m_bmp);
stretch_blit(
static_cast<const Alleg4Surface*>(src)->m_bmp, m_bmp,
srcRect.x, srcRect.y, srcRect.w, srcRect.h,
dstRect.x, dstRect.y, dstRect.w, dstRect.h);
}
} // namespace she

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2017 David Capello
// Copyright (C) 2012-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -63,6 +63,7 @@ namespace she {
void drawSurface(const Surface* src, int dstx, int dsty) override;
void drawRgbaSurface(const Surface* src, int dstx, int dsty) override;
void drawRgbaSurface(const Surface* src, int srcx, int srcy, int dstx, int dsty, int w, int h) override;
void drawRgbaSurface(const Surface* src, const gfx::Rect& srcRect, const gfx::Rect& dstRect) override;
private:
BITMAP* m_bmp;

View File

@ -470,6 +470,21 @@ public:
SkCanvas::kStrict_SrcRectConstraint);
}
void drawRgbaSurface(const Surface* src, const gfx::Rect& srcRect, const gfx::Rect& dstRect) override {
SkRect srcRect2 = SkRect::Make(SkIRect::MakeXYWH(srcRect.x, srcRect.y, srcRect.w, srcRect.h));
SkRect dstRect2 = SkRect::Make(SkIRect::MakeXYWH(dstRect.x, dstRect.y, dstRect.w, dstRect.h));
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrcOver);
paint.setFilterQuality(srcRect.w < dstRect.w ||
srcRect.h < dstRect.h ? kNone_SkFilterQuality:
kHigh_SkFilterQuality);
m_canvas->drawBitmapRect(
((SkiaSurface*)src)->m_bitmap, srcRect2, dstRect2, &paint,
SkCanvas::kStrict_SrcRectConstraint);
}
void drawColoredRgbaSurface(const Surface* src, gfx::Color fg, gfx::Color bg, const gfx::Clip& clipbase) override {
gfx::Clip clip(clipbase);
if (!clip.clip(width(), height(), src->width(), src->height()))

View File

@ -1,5 +1,5 @@
// SHE library
// Copyright (C) 2012-2017 David Capello
// Copyright (C) 2012-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -66,6 +66,7 @@ namespace she {
virtual void drawSurface(const Surface* src, int dstx, int dsty) = 0;
virtual void drawRgbaSurface(const Surface* src, int dstx, int dsty) = 0;
virtual void drawRgbaSurface(const Surface* src, int srcx, int srcy, int dstx, int dsty, int width, int height) = 0;
virtual void drawRgbaSurface(const Surface* surface, const gfx::Rect& srcRect, const gfx::Rect& dstRect) = 0;
virtual void drawColoredRgbaSurface(const Surface* src, gfx::Color fg, gfx::Color bg, const gfx::Clip& clip) = 0;
virtual void applyScale(int scaleFactor) = 0;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -201,6 +201,21 @@ void Graphics::drawRgbaSurface(she::Surface* surface, int srcx, int srcy, int ds
m_surface->drawRgbaSurface(surface, srcx, srcy, m_dx+dstx, m_dy+dsty, w, h);
}
void Graphics::drawRgbaSurface(she::Surface* surface,
const gfx::Rect& srcRect,
const gfx::Rect& dstRect)
{
dirty(gfx::Rect(m_dx+dstRect.x, m_dy+dstRect.y,
dstRect.w, dstRect.h));
she::SurfaceLock lockSrc(surface);
she::SurfaceLock lockDst(m_surface);
m_surface->drawRgbaSurface(
surface,
srcRect,
gfx::Rect(dstRect).offset(m_dx, m_dy));
}
void Graphics::drawColoredRgbaSurface(she::Surface* surface, gfx::Color color, int x, int y)
{
dirty(gfx::Rect(m_dx+x, m_dy+y, surface->width(), surface->height()));

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -75,6 +75,9 @@ namespace ui {
void drawSurface(she::Surface* surface, int x, int y);
void drawRgbaSurface(she::Surface* surface, int x, int y);
void drawRgbaSurface(she::Surface* surface, int srcx, int srcy, int dstx, int dsty, int w, int h);
void drawRgbaSurface(she::Surface* surface,
const gfx::Rect& srcRect,
const gfx::Rect& dstRect);
void drawColoredRgbaSurface(she::Surface* surface, gfx::Color color, int x, int y);
void drawColoredRgbaSurface(she::Surface* surface, gfx::Color color, int srcx, int srcy, int dstx, int dsty, int w, int h);