/*************************************************************************** cpaint_impl.cpp (c) 2000-2009 BenoƮt Minisini 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 #endif #include #include #include #include #ifndef NO_X_WINDOW #include #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 "cprinter.h" #include "csvgimage.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++; } else if (GB.Is(device, CLASS_Printer)) { CPRINTER *printer = (CPRINTER *)device; if (!printer->printing) { GB.Error("Printer is not printing"); return TRUE; } target = printer->printer; } else if (GB.Is(device, CLASS_SvgImage)) { CSVGIMAGE *svgimage = (CSVGIMAGE *)device; target = SVGIMAGE_init(svgimage); if (!target) { GB.Error("SvgImage size is not defined"); return TRUE; } } 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 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 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, TextExtents, 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; }