qt5-ukui-platformtheme/ukui-styles/qt5-style-ukui/breezeboxshadowrenderer.cpp

767 lines
24 KiB
C++

/*
* Copyright (C) 2018 Vlad Zagorodniy <vlad.zahorodnii@kde.org>
*
* The box blur implementation is based on AlphaBoxBlur from Firefox.
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// own
#include "breezeboxshadowrenderer.h"
#include <QX11Info>
#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <X11/X.h>
#include <xcb/xcb.h>
#include <QX11Info>
#include <X11/extensions/shape.h>
// auto-generated
// Qt
#include <QPainter>
#ifdef BREEZE_COMMON_USE_KDE4
#include <QtCore/qmath.h>
#else
#include <QtMath>
#endif
static inline int calculateBlurRadius(qreal stdDev)
{
// See https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
const qreal gaussianScaleFactor = (3.0 * qSqrt(2.0 * M_PI) / 4.0) * 1.5;
return qMax(2, qFloor(stdDev * gaussianScaleFactor + 0.5));
}
static inline qreal calculateBlurStdDev(int radius)
{
// See https://www.w3.org/TR/css-backgrounds-3/#shadow-blur
return radius * 0.5;
}
static inline QSize calculateBlurExtent(int radius)
{
const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius));
return QSize(blurRadius, blurRadius);
}
struct BoxLobes
{
int left; ///< how many pixels sample to the left
int right; ///< how many pixels sample to the right
};
/**
* Compute box filter parameters.
*
* @param radius The blur radius.
* @returns Parameters for three box filters.
**/
static QVector<BoxLobes> computeLobes(int radius)
{
const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius));
const int z = blurRadius / 3;
int major;
int minor;
int final;
switch (blurRadius % 3) {
case 0:
major = z;
minor = z;
final = z;
break;
case 1:
major = z + 1;
minor = z;
final = z;
break;
case 2:
major = z + 1;
minor = z;
final = z + 1;
break;
default:
#if !BREEZE_COMMON_USE_KDE4
Q_UNREACHABLE();
#endif
break;
}
Q_ASSERT(major + minor + final == blurRadius);
return {
{major, minor},
{minor, major},
{final, final}
};
}
/**
* Process a row with a box filter.
*
* @param src The start of the row.
* @param dst The destination.
* @param width The width of the row, in pixels.
* @param horizontalStride The number of bytes from one alpha value to the
* next alpha value.
* @param verticalStride The number of bytes from one row to the next row.
* @param lobes Params of the box filter.
* @param transposeInput Whether the input is transposed.
* @param transposeOutput Whether the output should be transposed.
**/
static inline void boxBlurRowAlpha(const uint8_t *src, uint8_t *dst, int width, int horizontalStride,
int verticalStride, const BoxLobes &lobes, bool transposeInput,
bool transposeOutput)
{
const int inputStep = transposeInput ? verticalStride : horizontalStride;
const int outputStep = transposeOutput ? verticalStride : horizontalStride;
const int boxSize = lobes.left + 1 + lobes.right;
const int reciprocal = (1 << 24) / boxSize;
uint32_t alphaSum = (boxSize + 1) / 2;
const uint8_t *left = src;
const uint8_t *right = src;
uint8_t *out = dst;
const uint8_t firstValue = src[0];
const uint8_t lastValue = src[(width - 1) * inputStep];
alphaSum += firstValue * lobes.left;
const uint8_t *initEnd = src + (boxSize - lobes.left) * inputStep;
while (right < initEnd) {
alphaSum += *right;
right += inputStep;
}
const uint8_t *leftEnd = src + boxSize * inputStep;
while (right < leftEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += *right - firstValue;
right += inputStep;
out += outputStep;
}
const uint8_t *centerEnd = src + width * inputStep;
while (right < centerEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += *right - *left;
left += inputStep;
right += inputStep;
out += outputStep;
}
const uint8_t *rightEnd = dst + width * outputStep;
while (out < rightEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += lastValue - *left;
left += inputStep;
out += outputStep;
}
}
/**
* Blur the alpha channel of a given image.
*
* @param image The input image.
* @param radius The blur radius.
* @param rect Specifies what part of the image to blur. If nothing is provided, then
* the whole alpha channel of the input image will be blurred.
**/
static inline void boxBlurAlpha(QImage &image, int radius, const QRect &rect = {})
{
if (radius < 2) {
return;
}
const QVector<BoxLobes> lobes = computeLobes(radius);
const QRect blurRect = rect.isNull() ? image.rect() : rect;
const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;
const int width = blurRect.width();
const int height = blurRect.height();
const int rowStride = image.bytesPerLine();
const int pixelStride = image.depth() >> 3;
const int bufferStride = qMax(width, height) * pixelStride;
QScopedPointer<uint8_t, QScopedPointerArrayDeleter<uint8_t> > buf(new uint8_t[2 * bufferStride]);
uint8_t *buf1 = buf.data();
uint8_t *buf2 = buf1 + bufferStride;
// Blur the image in horizontal direction.
for (int i = 0; i < height; ++i) {
uint8_t *row = image.scanLine(blurRect.y() + i) + blurRect.x() * pixelStride + alphaOffset;
boxBlurRowAlpha(row, buf1, width, pixelStride, rowStride, lobes[0], false, false);
boxBlurRowAlpha(buf1, buf2, width, pixelStride, rowStride, lobes[1], false, false);
boxBlurRowAlpha(buf2, row, width, pixelStride, rowStride, lobes[2], false, false);
}
// Blur the image in vertical direction.
for (int i = 0; i < width; ++i) {
uint8_t *column = image.scanLine(blurRect.y()) + (blurRect.x() + i) * pixelStride + alphaOffset;
boxBlurRowAlpha(column, buf1, height, pixelStride, rowStride, lobes[0], true, false);
boxBlurRowAlpha(buf1, buf2, height, pixelStride, rowStride, lobes[1], false, false);
boxBlurRowAlpha(buf2, column, height, pixelStride, rowStride, lobes[2], false, true);
}
}
static inline void mirrorTopLeftQuadrant(QImage &image)
{
const int width = image.width();
const int height = image.height();
const int centerX = qCeil(width * 0.5);
const int centerY = qCeil(height * 0.5);
const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;
const int stride = image.depth() >> 3;
for (int y = 0; y < centerY; ++y) {
uint8_t *in = image.scanLine(y) + alphaOffset;
uint8_t *out = in + (width - 1) * stride;
for (int x = 0; x < centerX; ++x, in += stride, out -= stride) {
*out = *in;
}
}
for (int y = 0; y < centerY; ++y) {
const uint8_t *in = image.scanLine(y) + alphaOffset;
uint8_t *out = image.scanLine(width - y - 1) + alphaOffset;
for (int x = 0; x < width; ++x, in += stride, out += stride) {
*out = *in;
}
}
}
static void renderShadow(QPainter *painter, const QRect &rect, qreal borderRadius, const QPoint &offset, int radius, const QColor &color)
{
const QSize inflation = calculateBlurExtent(radius);
const QSize size = rect.size() + 2 * inflation;
#if BREEZE_COMMON_USE_KDE4
const qreal dpr = 1.0;
#else
const qreal dpr = painter->device()->devicePixelRatioF();
#endif
QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied);
#if !BREEZE_COMMON_USE_KDE4
shadow.setDevicePixelRatio(dpr);
#endif
shadow.fill(Qt::transparent);
QRect boxRect(QPoint(0, 0), rect.size());
boxRect.moveCenter(QRect(QPoint(0, 0), size).center());
const qreal xRadius = 2.0 * borderRadius / boxRect.width();
const qreal yRadius = 2.0 * borderRadius / boxRect.height();
QPainter shadowPainter;
shadowPainter.begin(&shadow);
shadowPainter.setRenderHint(QPainter::Antialiasing);
shadowPainter.setPen(Qt::NoPen);
shadowPainter.setBrush(Qt::black);
shadowPainter.drawRoundedRect(boxRect, xRadius, yRadius);
shadowPainter.end();
// Because the shadow texture is symmetrical, that's enough to blur
// only the top-left quadrant and then mirror it.
const QRect blurRect(0, 0, qCeil(shadow.width() * 0.5), qCeil(shadow.height() * 0.5));
const int scaledRadius = qRound(radius * dpr);
boxBlurAlpha(shadow, scaledRadius, blurRect);
mirrorTopLeftQuadrant(shadow);
// Give the shadow a tint of the desired color.
shadowPainter.begin(&shadow);
shadowPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
shadowPainter.fillRect(shadow.rect(), color);
shadowPainter.end();
// Actually, present the shadow.
QRect shadowRect = shadow.rect();
shadowRect.setSize(shadowRect.size() / dpr);
shadowRect.moveCenter(rect.center() + offset);
painter->drawImage(shadowRect, shadow);
}
void BoxShadowRenderer::setBoxSize(const QSize &size)
{
m_boxSize = size;
}
void BoxShadowRenderer::setBorderRadius(qreal radius)
{
m_borderRadius = radius;
}
void BoxShadowRenderer::setDevicePixelRatio(qreal dpr)
{
m_dpr = dpr;
}
void BoxShadowRenderer::addShadow(const QPoint &offset, int radius, const QColor &color)
{
Shadow shadow = {};
shadow.offset = offset;
shadow.radius = radius;
shadow.color = color;
m_shadows.append(shadow);
}
QImage BoxShadowRenderer::render() const
{
if (m_shadows.isEmpty()) {
return {};
}
QSize canvasSize;
#if BREEZE_COMMON_USE_KDE4
foreach (const Shadow &shadow, m_shadows) {
#else
for (const Shadow &shadow : qAsConst(m_shadows)) {
#endif
canvasSize = canvasSize.expandedTo(
calculateMinimumShadowTextureSize(m_boxSize, shadow.radius, shadow.offset));
}
QImage canvas(canvasSize * m_dpr, QImage::Format_ARGB32_Premultiplied);
#if !BREEZE_COMMON_USE_KDE4
canvas.setDevicePixelRatio(m_dpr);
#endif
canvas.fill(Qt::transparent);
QRect boxRect(QPoint(0, 0), m_boxSize);
boxRect.moveCenter(QRect(QPoint(0, 0), canvasSize).center());
QPainter painter(&canvas);
#if BREEZE_COMMON_USE_KDE4
foreach (const Shadow &shadow, m_shadows) {
#else
for (const Shadow &shadow : qAsConst(m_shadows)) {
#endif
renderShadow(&painter, boxRect, m_borderRadius, shadow.offset, shadow.radius, shadow.color);
}
painter.end();
return canvas;
}
QSize BoxShadowRenderer::calculateMinimumBoxSize(int radius)
{
const QSize blurExtent = calculateBlurExtent(radius);
return 2 * blurExtent + QSize(1, 1);
}
QSize BoxShadowRenderer::calculateMinimumShadowTextureSize(const QSize &boxSize, int radius, const QPoint &offset)
{
return boxSize + 2 * calculateBlurExtent(radius) + QSize(qAbs(offset.x()), qAbs(offset.y()));
}
BreezeShadowHelper::BreezeShadowHelper( QObject* parent):
QObject( parent )
{
// delay till event dispatcher is running as Wayland is highly async
// QMetaObject::invokeMethod(this, "initializeWayland", Qt::QueuedConnection);
}
//_______________________________________________________
BreezeShadowHelper::~BreezeShadowHelper()
{
#if BREEZE_HAVE_X11
if( Helper::isX11() )
{ foreach( const quint32& value, _pixmaps ) xcb_free_pixmap( Helper::connection(), value ); }
#endif
}
//_______________________________________________________
bool BreezeShadowHelper::eventFilter( QObject* object, QEvent* event )
{
// check event type
if( event->type() != QEvent::WinIdChange ) return false;
// cast widget
QWidget* widget( static_cast<QWidget*>( object ) );
// install shadows and update winId
installShadows( widget );
return false;
}
//_______________________________________________________
bool BreezeShadowHelper::installShadows(const QWidget* widget )
{
qDebug() << "install shadows:" << (widget == nullptr);
if( !widget ) return false;
/*
From bespin code. Supposedly prevent playing with some 'pseudo-widgets'
that have winId matching some other -random- window
*/
// if( !(widget->testAttribute(Qt::WA_WState_Created) && widget->internalWinId() ))
// { return false; }
// create shadow tiles if needed
const qreal frameRadius = 0;
QColor color1(255,0,0);
QColor color2(0,255,0);
const qreal dpr =1;
const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(20)
.expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(10));
// color1.setAlphaF(0.22);
// color2.setAlphaF(0.12);
BoxShadowRenderer shadowRenderer;
shadowRenderer.setBorderRadius(frameRadius);
shadowRenderer.setBoxSize(boxSize);
shadowRenderer.setDevicePixelRatio(dpr);
shadowRenderer.addShadow(QPoint(0, 0), 20, color1);
shadowRenderer.addShadow(QPoint(0, -3), 10, color2);
QImage shadowTexture = shadowRenderer.render();
const QRect outerRect(QPoint(0, 0), shadowTexture.size() / dpr);
QRect boxRect(QPoint(0, 0), boxSize);
boxRect.moveCenter(outerRect.center());
// Mask out inner rect.
QPainter painter(&shadowTexture);
painter.setRenderHint(QPainter::Antialiasing);
const QMargins margins = QMargins(
boxRect.left() - outerRect.left() - 2 - 0,
boxRect.top() - outerRect.top() - 2 - 5,
outerRect.right() - boxRect.right() - 2 + 0,
outerRect.bottom() - boxRect.bottom() - 2 + 5);
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
// painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.drawRoundedRect(
outerRect - margins,
frameRadius,
frameRadius);
// We're done.
painter.end();
const QPoint innerRectTopLeft = outerRect.center();
qDebug() << "innerRectTopLeft:" << innerRectTopLeft;
_shadowTiles = TileSet(
QPixmap::fromImage(shadowTexture),
innerRectTopLeft.x(),
innerRectTopLeft.y(),
1, 1);
if( !_shadowTiles.isValid() ) return false;
QVector<quint32> data( createPixmapHandles() );
if( data.size() != 8 ) return false;
// const QMargins margins(10,10,10,10);
const quint32 topSize = margins.top();
const quint32 bottomSize = margins.bottom();
const quint32 leftSize( margins.left() );
const quint32 rightSize( margins.right() );
// assign to data and xcb property
data << QVector<quint32>{topSize, rightSize, bottomSize, leftSize};
xcb_change_property( QX11Info::connection(), XCB_PROP_MODE_REPLACE, widget->winId(), _atom, XCB_ATOM_CARDINAL, 32, data.size(), data.constData() );
xcb_flush( QX11Info::connection() );
return false;
}
const QVector<quint32>& BreezeShadowHelper::createPixmapHandles()
{
/**
shadow atom and property specification available at
https://community.kde.org/KWin/Shadow
*/
// // create atom
// #if BREEZE_HAVE_X11
// if( !_atom && Helper::isX11() ) _atom = _helper.createAtom( QLatin1String( netWMShadowAtomName ) );
// #endif
QString name = "_KDE_NET_WM_SHADOW";
xcb_connection_t* connection( QX11Info::connection() );
xcb_intern_atom_cookie_t cookie( xcb_intern_atom( connection, false, name.size(), qPrintable( name ) ) );
ScopedPointer<xcb_intern_atom_reply_t> reply( xcb_intern_atom_reply( connection, cookie, nullptr) );
//return reply ? reply->atom:0;
_atom = reply ? reply->atom:0;//xcb_intern_atom_reply(QX11Info::connection(), xcb_intern_atom(QX11Info::connection(), 0, strlen("_NET_WM_SHADOW"), "_NET_WM_SHADOW"), NULL)->atom;
// make sure size is valid
if( _pixmaps.empty() )
{
_pixmaps = QVector<quint32> {
createPixmap( _shadowTiles.pixmap( 1 ) ),
createPixmap( _shadowTiles.pixmap( 2 ) ),
createPixmap( _shadowTiles.pixmap( 5 ) ),
createPixmap( _shadowTiles.pixmap( 8 ) ),
createPixmap( _shadowTiles.pixmap( 7 ) ),
createPixmap( _shadowTiles.pixmap( 6 ) ),
createPixmap( _shadowTiles.pixmap( 3 ) ),
createPixmap( _shadowTiles.pixmap( 0 ) )
};
}
// return relevant list of pixmap handles
return _pixmaps;
}
quint32 BreezeShadowHelper::createPixmap( const QPixmap& source )
{
// do nothing for invalid pixmaps
if( source.isNull() ) return 0;
// if( !Helper::isX11() ) return 0;
/*
in some cases, pixmap handle is invalid. This is the case notably
when Qt uses to RasterEngine. In this case, we create an X11 Pixmap
explicitly and draw the source pixmap on it.
*/
// #if BREEZE_HAVE_X11
const int width( source.width() );
const int height( source.height() );
// create X11 pixmap
xcb_pixmap_t pixmap = xcb_generate_id( QX11Info::connection() );
xcb_create_pixmap( QX11Info::connection(), 32, pixmap, QX11Info::appRootWindow(), width, height );
xcb_gcontext_t _gc = xcb_generate_id( QX11Info::connection());
xcb_create_gc( QX11Info::connection(), _gc, pixmap, 0, nullptr );
// create image from QPixmap and assign to pixmap
QImage image( source.toImage() );
#if QT_VERSION >= 0x051000
xcb_put_image( QX11Info::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, image.width(), image.height(), 0, 0, 0, 32, image.sizeInBytes(), image.constBits());
#else
xcb_put_image( QX11Info::connection(), XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, image.width(), image.height(), 0, 0, 0, 32, image.byteCount(), image.constBits());
#endif
return pixmap;
// #else
// return 0;
// #endif
}
//___________________________________________________________
inline bool bits(TileSet::Tiles flags, TileSet::Tiles testFlags)
{ return (flags & testFlags) == testFlags; }
//______________________________________________________________________________________
inline qreal devicePixelRatio( const QPixmap& pixmap )
{
#if QT_VERSION >= 0x050300
return pixmap.devicePixelRatio();
#else
Q_UNUSED( pixmap );
return 1;
#endif
}
//______________________________________________________________________________________
inline void setDevicePixelRatio( QPixmap& pixmap, qreal value )
{
#if QT_VERSION >= 0x050300
return pixmap.setDevicePixelRatio( value );
#else
Q_UNUSED( pixmap );
Q_UNUSED( value );
#endif
}
//______________________________________________________________
void TileSet::initPixmap( PixmapList& pixmaps, const QPixmap &source, int width, int height, const QRect &rect)
{
QSize size( width, height );
if( !( size.isValid() && rect.isValid() ) )
{
pixmaps.append( QPixmap() );
} else if( size != rect.size() ) {
const qreal dpiRatio( devicePixelRatio( source ) );
const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio );
const QSize scaledSize( size*dpiRatio );
const QPixmap tile( source.copy(scaledRect) );
QPixmap pixmap( scaledSize );
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.drawTiledPixmap(0, 0, scaledSize.width(), scaledSize.height(), tile);
setDevicePixelRatio( pixmap, dpiRatio );
pixmaps.append( pixmap );
} else {
const qreal dpiRatio( devicePixelRatio( source ) );
const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio );
QPixmap pixmap( source.copy( scaledRect ) );
setDevicePixelRatio( pixmap, dpiRatio );
pixmaps.append( pixmap );
}
}
//______________________________________________________________
TileSet::TileSet():
_w1(0),
_h1(0),
_w3(0),
_h3(0)
{ _pixmaps.reserve(9); }
//______________________________________________________________
TileSet::TileSet(const QPixmap &source, int w1, int h1, int w2, int h2 ):
_w1(w1),
_h1(h1),
_w3(0),
_h3(0)
{
_pixmaps.reserve(9);
if( source.isNull() ) return;
_w3 = source.width()/devicePixelRatio( source ) - (w1 + w2);
_h3 = source.height()/devicePixelRatio( source ) - (h1 + h2);
int w = w2;
int h = h2;
// initialise pixmap array
initPixmap( _pixmaps, source, _w1, _h1, QRect(0, 0, _w1, _h1) );
initPixmap( _pixmaps, source, w, _h1, QRect(_w1, 0, w2, _h1) );
initPixmap( _pixmaps, source, _w3, _h1, QRect(_w1+w2, 0, _w3, _h1) );
initPixmap( _pixmaps, source, _w1, h, QRect(0, _h1, _w1, h2) );
initPixmap( _pixmaps, source, w, h, QRect(_w1, _h1, w2, h2) );
initPixmap( _pixmaps, source, _w3, h, QRect(_w1+w2, _h1, _w3, h2) );
initPixmap( _pixmaps, source, _w1, _h3, QRect(0, _h1+h2, _w1, _h3) );
initPixmap( _pixmaps, source, w, _h3, QRect(_w1, _h1+h2, w2, _h3) );
initPixmap( _pixmaps, source, _w3, _h3, QRect(_w1+w2, _h1+h2, _w3, _h3) );
}
//___________________________________________________________
void TileSet::render(const QRect &constRect, QPainter *painter, Tiles tiles) const
{
const bool oldHint( painter->testRenderHint( QPainter::SmoothPixmapTransform ) );
painter->setRenderHint( QPainter::SmoothPixmapTransform, true );
// check initialization
if( _pixmaps.size() < 9 ) return;
// copy source rect
QRect rect( constRect );
// get rect dimensions
int x0, y0, w, h;
rect.getRect(&x0, &y0, &w, &h);
// calculate pixmaps widths
int wLeft(0);
int wRight(0);
if( _w1+_w3 > 0 )
{
qreal wRatio( qreal( _w1 )/qreal( _w1 + _w3 ) );
wLeft = (tiles&Right) ? qMin( _w1, int(w*wRatio) ):_w1;
wRight = (tiles&Left) ? qMin( _w3, int(w*(1.0-wRatio)) ):_w3;
}
// calculate pixmap heights
int hTop(0);
int hBottom(0);
if( _h1+_h3 > 0 )
{
qreal hRatio( qreal( _h1 )/qreal( _h1 + _h3 ) );
hTop = (tiles&Bottom) ? qMin( _h1, int(h*hRatio) ):_h1;
hBottom = (tiles&Top) ? qMin( _h3, int(h*(1.0-hRatio)) ):_h3;
}
// calculate corner locations
w -= wLeft + wRight;
h -= hTop + hBottom;
const int x1 = x0 + wLeft;
const int x2 = x1 + w;
const int y1 = y0 + hTop;
const int y2 = y1 + h;
const int w2 = _pixmaps.at(7).width()/devicePixelRatio( _pixmaps.at(7) );
const int h2 = _pixmaps.at(5).height()/devicePixelRatio( _pixmaps.at(5) );
// corner
if( bits( tiles, TileSet::Tile(Top|Left)) ) painter->drawPixmap(x0, y0, _pixmaps.at(0), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(0) ), hTop*devicePixelRatio( _pixmaps.at(0) ));
if( bits( tiles, TileSet::Tile(Top|Right)) ) painter->drawPixmap(x2, y0, _pixmaps.at(2), (_w3-wRight)*devicePixelRatio( _pixmaps.at(2) ), 0, wRight*devicePixelRatio( _pixmaps.at(2) ), hTop*devicePixelRatio( _pixmaps.at(2) ) );
if( bits( tiles, TileSet::Tile(Bottom|Left)) ) painter->drawPixmap(x0, y2, _pixmaps.at(6), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(6) ), wLeft*devicePixelRatio( _pixmaps.at(6) ), hBottom*devicePixelRatio( _pixmaps.at(6) ));
if( bits( tiles, TileSet::Tile(Bottom|Right)) ) painter->drawPixmap(x2, y2, _pixmaps.at(8), (_w3-wRight)*devicePixelRatio( _pixmaps.at(8) ), (_h3-hBottom)*devicePixelRatio( _pixmaps.at(8) ), wRight*devicePixelRatio( _pixmaps.at(8) ), hBottom*devicePixelRatio( _pixmaps.at(8) ) );
// top and bottom
if( w > 0 )
{
if( tiles&Top ) painter->drawPixmap(x1, y0, w, hTop, _pixmaps.at(1), 0, 0, w2*devicePixelRatio( _pixmaps.at(1) ), hTop*devicePixelRatio( _pixmaps.at(1) ) );
if( tiles&Bottom ) painter->drawPixmap(x1, y2, w, hBottom, _pixmaps.at(7), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(7) ), w2*devicePixelRatio( _pixmaps.at(7) ), hBottom*devicePixelRatio( _pixmaps.at(7) ) );
}
// left and right
if( h > 0 )
{
if( tiles&Left ) painter->drawPixmap(x0, y1, wLeft, h, _pixmaps.at(3), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(3) ), h2*devicePixelRatio( _pixmaps.at(3) ) );
if( tiles&Right ) painter->drawPixmap(x2, y1, wRight, h, _pixmaps.at(5), (_w3-wRight)*devicePixelRatio( _pixmaps.at(5) ), 0, wRight*devicePixelRatio( _pixmaps.at(5) ), h2*devicePixelRatio( _pixmaps.at(5) ) );
}
// center
if( (tiles&Center) && h > 0 && w > 0 ) painter->drawPixmap(x1, y1, w, h, _pixmaps.at(4));
// restore
painter->setRenderHint( QPainter::SmoothPixmapTransform, oldHint );
}