mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-01 18:00:26 +00:00
New render engine to use bicubic interpolation when zoom < %100 (fix #1671)
This only works with Skia back-end.
This commit is contained in:
parent
e06735a4c0
commit
511752fea7
@ -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" />
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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()))
|
||||
|
@ -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;
|
||||
|
@ -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()));
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user