gambas-source-code/gb.qt4/src/cpaint_impl.cpp

859 lines
19 KiB
C++
Raw Normal View History

/***************************************************************************
cpaint_impl.cpp
(c) 2000-2009 Benoît Minisini <gambas@users.sourceforge.net>
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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
***************************************************************************/
#define __CPAINT_IMPL_CPP
#ifdef OS_SOLARIS
/* Make math.h define M_PI and a few other things */
#define __EXTENSIONS__
/* Get definition for finite() */
#include <ieeefp.h>
#endif
#include <math.h>
#include <QPainter>
#include <QPen>
#include <QBrush>
#ifndef NO_X_WINDOW
#include <QX11Info>
#endif
#include "gambas.h"
#include "CConst.h"
#include "CFont.h"
#include "CWidget.h"
#include "CWindow.h"
#include "CPicture.h"
#include "CImage.h"
#include "CDrawingArea.h"
#include "CColor.h"
#include "CDraw.h"
#include "cpaint_impl.h"
#define EXTRA(d) ((QT_PAINT_EXTRA *)d->extra)
#define COLOR_TO_INT(color) ((color).rgba() ^ 0xFF000000)
#define MASK_COLOR(col) ((col & 0xFF000000) ? Qt::color0 : Qt::color1)
#define PAINTER(d) EXTRA(d)->painter
#define PATH(d) EXTRA(d)->path
#define CLIP(d) EXTRA(d)->clip
static inline qreal to_deg(float angle)
{
return (qreal)(angle * 180 / M_PI);
}
static bool init_painting(GB_PAINT *d, QPaintDevice *device)
{
QPen pen;
d->width = device->width();
d->height = device->height();
d->resolutionX = device->physicalDpiX();
d->resolutionY = device->physicalDpiY();
if (device->paintingActive())
{
GB.Error("Device already being painted");
return TRUE;
}
EXTRA(d)->painter = new QPainter(device);
EXTRA(d)->path = 0;
EXTRA(d)->clip = 0;
PAINTER(d)->setRenderHints(QPainter::Antialiasing, true);
PAINTER(d)->setRenderHints(QPainter::TextAntialiasing, true);
PAINTER(d)->setRenderHints(QPainter::SmoothPixmapTransform, true);
pen = PAINTER(d)->pen();
pen.setCapStyle(Qt::FlatCap);
pen.setJoinStyle(Qt::MiterJoin);
pen.setMiterLimit(10.0);
pen.setWidthF(2.0);
PAINTER(d)->setPen(pen);
PAINTER(d)->setBrush(Qt::black);
return FALSE;
}
static int Begin(GB_PAINT *d)
{
void *device = d->device;
QPaintDevice *target = NULL;
if (GB.Is(device, CLASS_Picture))
{
QPixmap *pixmap = ((CPICTURE *)device)->pixmap;
if (pixmap->isNull())
{
GB.Error("Bad picture");
return TRUE;
}
target = pixmap;
}
else if (GB.Is(device, CLASS_Image))
{
QImage *image = CIMAGE_get((CIMAGE *)device);
if (image->isNull())
{
GB.Error("Bad image");
return TRUE;
}
target = image;
}
else if (GB.Is(device, CLASS_DrawingArea))
{
MyDrawingArea *wid = (MyDrawingArea *)(((CWIDGET *)device)->widget);
if (wid->isCached())
target = wid->background();
else if (wid->cache)
target = wid->cache;
else
target = wid;
wid->drawn++;
}
return init_painting(d, target);
}
static void End(GB_PAINT *d)
{
void *device = d->device;
if (GB.Is(device, CLASS_DrawingArea))
{
MyDrawingArea *wid = (MyDrawingArea *)(((CWIDGET *)device)->widget);
if (wid)
{
if (wid->isCached())
{
wid->setBackground();
wid->refreshBackground();
}
wid->drawn--;
}
}
delete EXTRA(d)->path;
delete EXTRA(d)->clip;
delete EXTRA(d)->painter;
}
static void Save(GB_PAINT *d)
{
PAINTER(d)->save();
}
static void Restore(GB_PAINT *d)
{
PAINTER(d)->restore();
}
static void apply_font(QFont &font, void *object = 0)
{
GB_PAINT *d = (GB_PAINT *)DRAW.Paint.GetCurrent();
PAINTER(d)->setFont(font);
}
static void Font(GB_PAINT *d, int set, GB_FONT *font)
{
if (set)
PAINTER(d)->setFont(*((CFONT *)(*font))->font);
else
*font = CFONT_create(PAINTER(d)->font(), apply_font);
}
static void init_path(GB_PAINT *d)
{
switch (EXTRA(d)->fillRule)
{
case GB_PAINT_FILL_RULE_WINDING:
PATH(d)->setFillRule(Qt::WindingFill);
break;
case GB_PAINT_FILL_RULE_EVEN_ODD:
default:
PATH(d)->setFillRule(Qt::OddEvenFill);
}
}
#define CHECK_PATH(_d) \
if (!PATH(_d)) \
return; \
else \
init_path(_d);
#define PRESERVE_PATH(_d, _p) \
if (!(_p)) \
{ \
delete PATH(_d); \
EXTRA(_d)->path = 0; \
}
#define CREATE_PATH(_d) \
if (!PATH(_d)) \
EXTRA(_d)->path = new QPainterPath();
static void Clip(GB_PAINT *d, int preserve)
{
CHECK_PATH(d);
QPainterPath path = PAINTER(d)->worldTransform().map(*PATH(d));
if (CLIP(d))
path = CLIP(d)->intersected(path);
delete EXTRA(d)->clip;
EXTRA(d)->clip = new QPainterPath(path);
PRESERVE_PATH(d, preserve);
}
static void ResetClip(GB_PAINT *d)
{
delete CLIP(d);
EXTRA(d)->clip = 0;
}
static void get_path_extents(QPainterPath *path, GB_EXTENTS *ext)
{
if (!path)
{
ext->x1 = ext->x2 = ext->y1 = ext->y2 = 0.0;
return;
}
QRectF rect = path->boundingRect();
ext->x1 = (float)rect.left();
ext->y1 = (float)rect.top();
ext->x2 = (float)rect.right();
ext->y2 = (float)rect.bottom();
}
static void ClipExtents(GB_PAINT *d, GB_EXTENTS *ext)
{
get_path_extents(CLIP(d), ext);
}
static void Fill(GB_PAINT *d, int preserve)
{
CHECK_PATH(d);
if (!CLIP(d))
PAINTER(d)->fillPath(*PATH(d), PAINTER(d)->brush());
else
{
QPainterPath path = PAINTER(d)->worldTransform().inverted().map(*CLIP(d));
path = path.intersected(*PATH(d));
PAINTER(d)->fillPath(path, PAINTER(d)->brush());
}
PRESERVE_PATH(d, preserve);
}
static void Stroke(GB_PAINT *d, int preserve)
{
CHECK_PATH(d);
if (!CLIP(d))
PAINTER(d)->strokePath(*PATH(d), PAINTER(d)->pen());
else
{
QPainterPathStroker stroker;
QPen pen = PAINTER(d)->pen();
stroker.setCapStyle(pen.capStyle());
stroker.setDashOffset(pen.dashOffset());
stroker.setDashPattern(pen.dashPattern());
stroker.setJoinStyle(pen.joinStyle());
stroker.setMiterLimit(pen.miterLimit());
stroker.setWidth(pen.widthF());
QPainterPath path = PAINTER(d)->worldTransform().inverted().map(*CLIP(d));
path = path.intersected(stroker.createStroke(*PATH(d)));
PAINTER(d)->fillPath(path, PAINTER(d)->brush());
}
PRESERVE_PATH(d, preserve);
}
static void PathExtents(GB_PAINT *d, GB_EXTENTS *ext)
{
get_path_extents(PATH(d), ext);
}
static int PathContains(GB_PAINT *d, float x, float y)
{
if (!PATH(d))
return FALSE;
QPointF point((qreal)x, (qreal)y);
return PATH(d)->contains(point);
}
static void Dash(GB_PAINT *d, int set, float **dashes, int *count)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
if (!*count)
pen.setStyle(Qt::SolidLine);
else
{
QVector<qreal> dv;
for (int i = 0; i < *count; i++)
dv << (qreal)(*dashes)[i];
pen.setStyle(Qt::CustomDashLine);
pen.setDashPattern(dv);
}
PAINTER(d)->setPen(pen);
}
else
{
if (pen.style() == Qt::CustomDashLine)
{
QVector<qreal> dv = pen.dashPattern();
*count = dv.count();
GB.Alloc(POINTER(dashes), sizeof(float) * *count);
for (int i = 0; i < *count; i++)
(*dashes)[i] = (float)dv[i];
}
else
{
*count = 0;
*dashes = NULL;
}
}
}
static void DashOffset(GB_PAINT *d, int set, float *offset)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
pen.setDashOffset((qreal)*offset);
PAINTER(d)->setPen(pen);
}
else
{
*offset = (float)pen.dashOffset();
}
}
static void FillRule(GB_PAINT *d, int set, int *value)
{
if (set)
{
EXTRA(d)->fillRule = *value;
}
else
*value = EXTRA(d)->fillRule;
}
static void LineCap(GB_PAINT *d, int set, int *value)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
switch (*value)
{
case GB_PAINT_LINE_CAP_ROUND:
pen.setCapStyle(Qt::RoundCap); break;
case GB_PAINT_LINE_CAP_SQUARE:
pen.setCapStyle(Qt::SquareCap); break;
case GB_PAINT_LINE_CAP_BUTT:
default:
pen.setCapStyle(Qt::FlatCap);
}
PAINTER(d)->setPen(pen);
}
else
{
switch (pen.capStyle())
{
case Qt::RoundCap: *value = GB_PAINT_LINE_CAP_ROUND; break;
case Qt::SquareCap: *value = GB_PAINT_LINE_CAP_SQUARE; break;
case Qt::FlatCap: default: *value = GB_PAINT_LINE_CAP_BUTT;
}
}
}
static void LineJoin(GB_PAINT *d, int set, int *value)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
switch (*value)
{
case GB_PAINT_LINE_JOIN_ROUND:
pen.setJoinStyle(Qt::RoundJoin); break;
case GB_PAINT_LINE_JOIN_BEVEL:
pen.setJoinStyle(Qt::BevelJoin); break;
case GB_PAINT_LINE_JOIN_MITER:
default:
pen.setJoinStyle(Qt::MiterJoin);
}
PAINTER(d)->setPen(pen);
}
else
{
switch (pen.joinStyle())
{
case Qt::RoundJoin: *value = GB_PAINT_LINE_JOIN_ROUND; break;
case Qt::BevelJoin: *value = GB_PAINT_LINE_JOIN_BEVEL; break;
case Qt::MiterJoin: default: *value = GB_PAINT_LINE_JOIN_MITER;
}
}
}
static void LineWidth(GB_PAINT *d, int set, float *value)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
pen.setWidthF((qreal)*value);
PAINTER(d)->setPen(pen);
}
else
*value = (float)pen.widthF();
}
static void MiterLimit(GB_PAINT *d, int set, float *value)
{
QPen pen = PAINTER(d)->pen();
if (set)
{
pen.setMiterLimit((qreal)*value);
PAINTER(d)->setPen(pen);
}
else
*value = (float)pen.miterLimit();
}
static void Operator(GB_PAINT *d, int set, int *value)
{
QPainter::CompositionMode mode;
if (set)
{
switch (*value)
{
case GB_PAINT_OPERATOR_CLEAR: mode = QPainter::CompositionMode_Clear; break;
case GB_PAINT_OPERATOR_SOURCE: mode = QPainter::CompositionMode_Source; break;
case GB_PAINT_OPERATOR_IN: mode = QPainter::CompositionMode_SourceIn; break;
case GB_PAINT_OPERATOR_OUT: mode = QPainter::CompositionMode_SourceOut; break;
case GB_PAINT_OPERATOR_ATOP: mode = QPainter::CompositionMode_SourceAtop; break;
case GB_PAINT_OPERATOR_DEST: mode = QPainter::CompositionMode_Destination; break;
case GB_PAINT_OPERATOR_DEST_OVER: mode = QPainter::CompositionMode_DestinationOver; break;
case GB_PAINT_OPERATOR_DEST_IN: mode = QPainter::CompositionMode_DestinationIn; break;
case GB_PAINT_OPERATOR_DEST_OUT: mode = QPainter::CompositionMode_DestinationOut; break;
case GB_PAINT_OPERATOR_DEST_ATOP: mode = QPainter::CompositionMode_DestinationAtop; break;
case GB_PAINT_OPERATOR_XOR: mode = QPainter::CompositionMode_Xor; break;
case GB_PAINT_OPERATOR_ADD: mode = QPainter::CompositionMode_Plus; break;
case GB_PAINT_OPERATOR_SATURATE: mode = QPainter::CompositionMode_Multiply; break;
case GB_PAINT_OPERATOR_OVER: default: mode = QPainter::CompositionMode_SourceOver;
}
PAINTER(d)->setCompositionMode(mode);
}
else
{
switch (PAINTER(d)->compositionMode())
{
case QPainter::CompositionMode_Clear: *value = GB_PAINT_OPERATOR_CLEAR; break;
case QPainter::CompositionMode_Source: *value = GB_PAINT_OPERATOR_SOURCE; break;
case QPainter::CompositionMode_SourceIn: *value = GB_PAINT_OPERATOR_IN; break;
case QPainter::CompositionMode_SourceOut: *value = GB_PAINT_OPERATOR_OUT; break;
case QPainter::CompositionMode_SourceAtop: *value = GB_PAINT_OPERATOR_ATOP; break;
case QPainter::CompositionMode_Destination: *value = GB_PAINT_OPERATOR_DEST; break;
case QPainter::CompositionMode_DestinationOver: *value = GB_PAINT_OPERATOR_DEST_OVER; break;
case QPainter::CompositionMode_DestinationIn: *value = GB_PAINT_OPERATOR_DEST_IN; break;
case QPainter::CompositionMode_DestinationOut: *value = GB_PAINT_OPERATOR_DEST_OUT; break;
case QPainter::CompositionMode_DestinationAtop: *value = GB_PAINT_OPERATOR_DEST_ATOP; break;
case QPainter::CompositionMode_Xor: *value = GB_PAINT_OPERATOR_XOR; break;
case QPainter::CompositionMode_Plus: *value = GB_PAINT_OPERATOR_ADD; break;
case QPainter::CompositionMode_Multiply: *value = GB_PAINT_OPERATOR_SATURATE; break;
case QPainter::CompositionMode_SourceOver: default: *value = GB_PAINT_OPERATOR_OVER;
}
}
}
static void NewPath(GB_PAINT *d)
{
PRESERVE_PATH(d, FALSE);
}
static void ClosePath(GB_PAINT *d)
{
CHECK_PATH(d);
PATH(d)->closeSubpath();
}
static void Arc(GB_PAINT *d, float xc, float yc, float radius, float angle, float length)
{
CREATE_PATH(d);
QRectF rect;
rect.setCoords((qreal)(xc - radius), (qreal)(yc - radius), (qreal)(xc + radius), (qreal)(yc + radius));
PATH(d)->arcMoveTo(rect, to_deg(angle));
PATH(d)->arcTo(rect, to_deg(angle), to_deg(length));
}
static void Rectangle(GB_PAINT *d, float x, float y, float width, float height)
{
CREATE_PATH(d);
PATH(d)->addRect((qreal)x, (qreal)y, (qreal)width, (qreal)height);
}
static void GetCurrentPoint(GB_PAINT *d, float *x, float *y)
{
if (!PATH(d))
{
*x = 0;
*y = 0;
return;
}
QPointF pt = PATH(d)->currentPosition();
*x = (float)pt.x();
*y = (float)pt.y();
}
static void MoveTo(GB_PAINT *d, float x, float y)
{
CREATE_PATH(d);
PATH(d)->moveTo((qreal)x, (qreal)y);
}
static void LineTo(GB_PAINT *d, float x, float y)
{
CREATE_PATH(d);
PATH(d)->lineTo((qreal)x, (qreal)y);
}
static void CurveTo(GB_PAINT *d, float x1, float y1, float x2, float y2, float x3, float y3)
{
CREATE_PATH(d);
PATH(d)->cubicTo(QPointF((qreal)x1, (qreal)y1), QPointF((qreal)x2, (qreal)y2), QPointF((qreal)x3, (qreal)y3));
}
static GB_PAINT *_draw_text_p;
static void draw_text_cb(float x, float y, QString &text)
{
PATH(_draw_text_p)->addText(x, y, PAINTER(_draw_text_p)->font(), text);
}
static void Text(GB_PAINT *d, const char *text, int len, float w, float h, int align)
{
QPointF pos;
CREATE_PATH(d);
pos = PATH(d)->currentPosition();
_draw_text_p = d;
if (w <= 0 || h <= 0)
pos.ry() -= PAINTER(d)->fontMetrics().ascent();
DRAW_text_with(PAINTER(d), text, len, pos.x(), pos.y(), w, h, align, draw_text_cb);
}
/*static void TextExtents(GB_PAINT *d, const char *text, int len, GB_EXTENTS *ext)
{
QRectF rect = PAINTER(d)->boundingRect(QRectF(0, 0, d->width, d->height), Qt::AlignLeft | Qt::AlignTop | Qt::TextSingleLine | Qt::TextIncludeTrailingSpaces, QString::fromUtf8(text, len));
ext->x1 = 0;
ext->y1 = 0;
ext->x2 = (float)rect.width();
ext->y2 = (float)rect.height();
}*/
static void Matrix(GB_PAINT *d, int set, GB_TRANSFORM matrix)
{
QTransform *t = (QTransform *)matrix;
if (set)
{
if (t)
PAINTER(d)->setWorldTransform(*t);
else
PAINTER(d)->resetTransform();
}
else
*t = PAINTER(d)->worldTransform();
}
static void SetBrush(GB_PAINT *d, GB_BRUSH brush)
{
QBrush *b = (QBrush *)brush;
PAINTER(d)->setBrush(*b);
QPen p = PAINTER(d)->pen();
p.setBrush(*b);
PAINTER(d)->setPen(p);
//PAINTER(d)->setBrushOrigin(QPointF((qreal)x, (qreal)y));
}
static void BrushFree(GB_BRUSH brush)
{
delete (QBrush *)brush;
}
static void BrushColor(GB_BRUSH *brush, GB_COLOR color)
{
QBrush *br = new QBrush(CCOLOR_make(color));
*brush = (GB_BRUSH)br;
}
static void BrushImage(GB_BRUSH *brush, GB_IMAGE image)
{
QImage *img = CIMAGE_get((CIMAGE *)image);
QBrush *br = new QBrush(*img);
*brush = (GB_BRUSH)br;
}
static void BrushLinearGradient(GB_BRUSH *brush, float x0, float y0, float x1, float y1, int nstop, double *positions, GB_COLOR *colors, int extend)
{
QLinearGradient gradient;
int i;
gradient.setStart((qreal)x0, (qreal)y0);
gradient.setFinalStop((qreal)x1, (qreal)y1);
for (i = 0; i < nstop; i++)
gradient.setColorAt((qreal)positions[i], CCOLOR_make(colors[i]));
switch (extend)
{
case GB_PAINT_EXTEND_REPEAT:
gradient.setSpread(QGradient::RepeatSpread); break;
case GB_PAINT_EXTEND_REFLECT:
gradient.setSpread(QGradient::ReflectSpread); break;
case GB_PAINT_EXTEND_PAD:
default:
gradient.setSpread(QGradient::PadSpread);
}
QBrush *br = new QBrush(gradient);
*brush = br;
}
static void BrushRadialGradient(GB_BRUSH *brush, float cx, float cy, float r, float fx, float fy, int nstop, double *positions, GB_COLOR *colors, int extend)
{
QRadialGradient gradient;
int i;
gradient.setCenter((qreal)cx, (qreal)cy);
gradient.setRadius((qreal)r);
gradient.setFocalPoint((qreal)fx, (qreal)fy);
for (i = 0; i < nstop; i++)
gradient.setColorAt((qreal)positions[i], CCOLOR_make(colors[i]));
switch (extend)
{
case GB_PAINT_EXTEND_REPEAT:
gradient.setSpread(QGradient::RepeatSpread); break;
case GB_PAINT_EXTEND_REFLECT:
gradient.setSpread(QGradient::ReflectSpread); break;
case GB_PAINT_EXTEND_PAD:
default:
gradient.setSpread(QGradient::PadSpread);
}
QBrush *br = new QBrush(gradient);
*brush = br;
}
static void BrushMatrix(GB_BRUSH brush, int set, GB_TRANSFORM matrix)
{
QBrush *b = (QBrush *)brush;
QTransform *t = (QTransform *)matrix;
if (set)
{
if (t)
b->setTransform(*t);
else
b->setTransform(QTransform());
}
else
*t = b->transform();
}
static void TransformCreate(GB_TRANSFORM *matrix)
{
*matrix = (GB_TRANSFORM)(new QTransform());
}
static void TransformDelete(GB_TRANSFORM *matrix)
{
delete (QTransform *)*matrix;
*matrix = 0;
}
static void TransformInit(GB_TRANSFORM matrix, float xx, float yx, float xy, float yy, float x0, float y0)
{
QTransform *t = (QTransform *)matrix;
QMatrix m((qreal)xx, (qreal)yx, (qreal)xy, (qreal)yy, (qreal)x0, (qreal)y0);
QTransform t2(m);
*t = t2;
}
static void TransformTranslate(GB_TRANSFORM matrix, float tx, float ty)
{
QTransform *t = (QTransform *)matrix;
t->translate((qreal)tx, (qreal)ty);
}
static void TransformScale(GB_TRANSFORM matrix, float sx, float sy)
{
QTransform *t = (QTransform *)matrix;
t->scale((qreal)sx, (qreal)sy);
}
static void TransformRotate(GB_TRANSFORM matrix, float angle)
{
QTransform *t = (QTransform *)matrix;
t->rotate(to_deg(-angle));
}
static int TransformInvert(GB_TRANSFORM matrix)
{
QTransform *t = (QTransform *)matrix;
bool inv;
QTransform it = t->inverted(&inv);
if (inv)
{
*t = it;
return FALSE;
}
else
return TRUE;
}
static void TransformMultiply(GB_TRANSFORM matrix, GB_TRANSFORM matrix2)
{
QTransform *t = (QTransform *)matrix;
QTransform *t2 = (QTransform *)matrix2;
*t = *t * *t2;
}
GB_PAINT_DESC PAINT_Interface = {
// Size of the GB_PAINT structure extra data
sizeof(QT_PAINT_EXTRA),
Begin,
End,
Save,
Restore,
Font,
Clip,
ResetClip,
ClipExtents,
Fill,
Stroke,
PathExtents,
PathContains,
Dash,
DashOffset,
FillRule,
LineCap,
LineJoin,
LineWidth,
MiterLimit,
Operator,
NewPath,
ClosePath,
Arc,
Rectangle,
GetCurrentPoint,
MoveTo,
LineTo,
CurveTo,
Text,
Matrix,
SetBrush,
{
BrushFree,
BrushColor,
BrushImage,
BrushLinearGradient,
BrushRadialGradient,
BrushMatrix,
},
{
TransformCreate,
TransformDelete,
TransformInit,
TransformTranslate,
TransformScale,
TransformRotate,
TransformInvert,
TransformMultiply
}
};
void PAINT_begin(void *device)
{
DRAW.Paint.Begin(device);
}
void PAINT_end()
{
DRAW.Paint.End();
}
QPainter *PAINT_get_current()
{
GB_PAINT *d = (GB_PAINT *)DRAW.Paint.GetCurrent();
return d ? PAINTER(d) : NULL;
}