You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
7.1 KiB
263 lines
7.1 KiB
/* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
|
|
* (c)DWTFYW
|
|
*
|
|
* This program is free software. It comes without any warranty, to
|
|
* the extent permitted by applicable law. You can redistribute it
|
|
* and/or modify it under the terms of the Do What The Fuck You Want
|
|
* To Public License, Version 2, as published by Sam Hocevar. See
|
|
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
|
*/
|
|
#include <QDebug>
|
|
|
|
#include "xcrimg.h"
|
|
|
|
#include <QApplication>
|
|
#include <QPaintEngine>
|
|
#include <QStringList>
|
|
#include <QStyle>
|
|
#include <QTextCodec>
|
|
#include <QTextStream>
|
|
|
|
#include <QX11Info>
|
|
|
|
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xcursor/Xcursor.h>
|
|
#include <X11/extensions/Xfixes.h>
|
|
|
|
|
|
inline static quint8 alphaPreMul (quint8 clr, quint8 alpha) {
|
|
quint32 c32 = clr, a32 = alpha;
|
|
c32 = c32*a32/255;
|
|
if (c32 > a32) c32 = a32;
|
|
return c32&0xff;
|
|
}
|
|
|
|
|
|
static int nominalCursorSize (int iconSize) {
|
|
for (int i = 512; i > 8; i /= 2) {
|
|
if (i < iconSize) return i;
|
|
if (int(i*0.75) < iconSize) return int(i*0.75);
|
|
}
|
|
return 8;
|
|
}
|
|
|
|
|
|
static void baPutDW (QByteArray &ba, quint32 v) {
|
|
ba.append('\0'); ba.append('\0');
|
|
ba.append('\0'); ba.append('\0');
|
|
uchar *d = (uchar *)ba.data();
|
|
d += ba.size()-4;
|
|
for (int f = 4; f > 0; f--, d++) {
|
|
*d = (v&0xff);
|
|
v >>= 8;
|
|
}
|
|
}
|
|
|
|
|
|
void XCursorImage::convertARGB2PreMul (QImage &img) {
|
|
switch (img.format()) {
|
|
case QImage::Format_ARGB32_Premultiplied: return;
|
|
case QImage::Format_ARGB32: break;
|
|
default: (void) img.convertToFormat(QImage::Format_ARGB32/*_Premultiplied*/);
|
|
}
|
|
(void) img.convertToFormat(QImage::Format_ARGB32_Premultiplied); // this shouldn't convert anything
|
|
// so convert it!
|
|
for (int y = img.height()-1; y >= 0; y--) {
|
|
quint8 *line = (quint8 *)img.scanLine(y);
|
|
for (int x = 0; x < img.width(); x++, line += 4) {
|
|
// convert to 'premultiplied'
|
|
line[0] = alphaPreMul(line[0], line[3]);
|
|
line[1] = alphaPreMul(line[1], line[3]);
|
|
line[2] = alphaPreMul(line[2], line[3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
XCursorImage::XCursorImage (const QString &aName, const QImage &aImg, int aXHot, int aYHot, quint32 aDelay, quint32 aCSize) :
|
|
mIsValid(true), mName(aName), mImage(0), mDelay(aDelay), mXHot(aXHot), mYHot(aYHot), mCSize(aCSize)
|
|
{
|
|
mImage = new QImage(aImg.copy());
|
|
convertARGB2PreMul(*mImage);
|
|
}
|
|
|
|
|
|
XCursorImage::XCursorImage (const QString &aName) :
|
|
mIsValid(false), mName(aName), mImage(0), mDelay(50), mXHot(0), mYHot(0)
|
|
{
|
|
}
|
|
|
|
|
|
XCursorImage::~XCursorImage () {
|
|
delete mImage; // or memory will leak!
|
|
}
|
|
|
|
|
|
QPixmap XCursorImage::icon () const {
|
|
if (mIcon.isNull()) mIcon = createIcon();
|
|
return mIcon;
|
|
}
|
|
|
|
|
|
QCursor XCursorImage::cursor () const {
|
|
return QCursor(icon(), mXHot, mYHot);
|
|
}
|
|
|
|
|
|
QImage XCursorImage::image (int size) const {
|
|
if (size == -1) size = XcursorGetDefaultSize(QX11Info::display());
|
|
if (!mImage) return QImage();
|
|
return mImage->copy();
|
|
}
|
|
|
|
|
|
QPixmap XCursorImage::createIcon () const {
|
|
QPixmap pixmap;
|
|
int iconSize = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize);
|
|
int cursorSize = nominalCursorSize(iconSize);
|
|
QSize size = QSize(iconSize, iconSize);
|
|
QImage img = image(cursorSize);
|
|
if (!img.isNull()) {
|
|
// Scale the image if it's larger than the preferred icon size
|
|
if (img.width() > size.width() || img.height() > size.height())
|
|
img = img.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
pixmap = QPixmap::fromImage(img);
|
|
}
|
|
return pixmap;
|
|
}
|
|
|
|
|
|
quint32 XCursorImage::xcurSize () const {
|
|
if (!mImage || !mIsValid) return 0;
|
|
quint32 res = 9*4; // header
|
|
res += mImage->width()*mImage->height()*4;
|
|
return res;
|
|
}
|
|
|
|
|
|
void XCursorImage::genXCursorImg (QByteArray &res) const {
|
|
if (!mImage || !mIsValid) return;
|
|
baPutDW(res, 36); // header size
|
|
baPutDW(res, 0xfffd0002); // chunk type
|
|
baPutDW(res, mCSize); // subtype
|
|
baPutDW(res, 1); // version
|
|
baPutDW(res, (quint32)mImage->width());
|
|
baPutDW(res, (quint32)mImage->height());
|
|
baPutDW(res, (quint32)mXHot);
|
|
baPutDW(res, (quint32)mYHot);
|
|
baPutDW(res, (quint32)mDelay);
|
|
// now put the pixels
|
|
QImage i = mImage->copy();
|
|
(void) i.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
|
//i.convertToFormat(QImage::Format_ARGB32);
|
|
for (int y = 0; y < i.height(); y++) {
|
|
const uchar *sl = i.scanLine(y);
|
|
const quint32 *d = (const quint32 *)sl;
|
|
for (int x = 0; x < i.width(); x++, d++) baPutDW(res, *d);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
XCursorImages::XCursorImages (const QString &aName, const QString &aPath) :
|
|
mName(aName), mPath(aPath), mTitle(""), mAuthor(""), mLicense(""),
|
|
mEMail(""), mSite(""), mDescr(""), mIM("")
|
|
{
|
|
}
|
|
|
|
|
|
XCursorImages::~XCursorImages () {
|
|
qDeleteAll(mList);
|
|
mList.clear();
|
|
}
|
|
|
|
|
|
QByteArray XCursorImages::genXCursor () const {
|
|
QByteArray res;
|
|
// build 'credits' block
|
|
QByteArray crdBA[7];
|
|
{
|
|
QStringList crb;
|
|
crb << mAuthor << mLicense << mDescr << mTitle << mEMail << mSite << mIM;
|
|
for (int f = 0; f < crb.size(); f++) {
|
|
QString s(crb[f]);
|
|
if (s.isEmpty()) crdBA[f].clear(); else crdBA[f] = s.toUtf8();
|
|
}
|
|
}
|
|
//
|
|
res.append("Xcur"); // signature
|
|
baPutDW(res, 16); // header size
|
|
baPutDW(res, 65536); // version number
|
|
// TOC
|
|
quint32 cnt = 0;
|
|
foreach (const XCursorImage *i, mList) if (i->xcurSize() > 0) cnt++;
|
|
// 'credits'
|
|
for (int f = 0; f < 7; f++) if (!crdBA[f].isEmpty()) cnt++;
|
|
//
|
|
baPutDW(res, cnt); // # of chunks
|
|
if (!cnt) return res;
|
|
quint32 dataOfs = cnt*(3*4)+16;
|
|
// add 'credits' chunks
|
|
for (int f = 0; f < 7; f++) {
|
|
if (crdBA[f].isEmpty()) continue;
|
|
baPutDW(res, 0xfffd0001); // entry type
|
|
baPutDW(res, f+1); // subtype
|
|
baPutDW(res, dataOfs); // offset
|
|
dataOfs += crdBA[f].size()+20;
|
|
}
|
|
// add image chunks
|
|
foreach (const XCursorImage *i, mList) {
|
|
quint32 isz = i->xcurSize();
|
|
if (!isz) continue;
|
|
baPutDW(res, 0xfffd0002); // entry type
|
|
baPutDW(res, i->csize()); // subtype
|
|
baPutDW(res, dataOfs); // offset
|
|
dataOfs += isz;
|
|
}
|
|
// credits
|
|
for (int f = 0; f < 7; f++) {
|
|
if (crdBA[f].isEmpty()) continue;
|
|
baPutDW(res, 20); // header size
|
|
baPutDW(res, 0xfffd0001); // entry type
|
|
baPutDW(res, f+1); // subtype
|
|
baPutDW(res, 1); // version
|
|
baPutDW(res, crdBA[f].size()); // length
|
|
res.append(crdBA[f]);
|
|
}
|
|
// images
|
|
foreach (const XCursorImage *i, mList) {
|
|
quint32 isz = i->xcurSize();
|
|
if (!isz) continue;
|
|
i->genXCursorImg(res);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
QImage XCursorImages::buildImage () const {
|
|
int width = 0, height = 0, cnt = 0;
|
|
foreach (const XCursorImage *i, mList) {
|
|
if (i->xcurSize() > 0) {
|
|
QImage img = i->image();
|
|
width = qMax(width, img.width());
|
|
height = qMax(height, img.height());
|
|
++cnt;
|
|
}
|
|
}
|
|
//qDebug() << cnt << "images; size" << width << "by" << height;
|
|
|
|
QImage res(width*cnt, height, QImage::Format_ARGB32);
|
|
QPainter p(&res);
|
|
int x = 0;
|
|
foreach (const XCursorImage *i, mList) {
|
|
if (i->xcurSize() > 0) {
|
|
QImage img = i->image();
|
|
p.drawImage(QPoint(x, 0), img);
|
|
x += img.width();
|
|
}
|
|
}
|
|
return res;
|
|
}
|