forked from openkylin/qt5-ukui-platformtheme
767 lines
24 KiB
C++
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 );
|
|
|
|
}
|
|
|