/*************************************************************************** File : EmfEngine.cpp Project : EmfEngine -------------------------------------------------------------------- Copyright : (C) 2009 - 2010 by Ion Vasilief Email (use @ for *) : ion_vasilief*yahoo.fr Description : Enables the export of QPainter grafics to Windows Enhanced Metafiles (.emf) by using GDI calls ***************************************************************************/ /*************************************************************************** * * * 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.1 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 * * * ***************************************************************************/ #include "EmfEngine.h" EmfPaintEngine::EmfPaintEngine(const QString& f) : QPaintEngine(QPaintEngine::AllFeatures) { fname = f; } bool EmfPaintEngine::begin(QPaintDevice* p) { if (!p) return false; setPaintDevice(p); HWND desktop = GetDesktopWindow(); HDC dc = GetDC(desktop); PCSTR description = "Metafile created\0with EmfEngine\0"; metaDC = CreateEnhMetaFileA(dc, fname.toLocal8Bit().data(), &d_rect, description);//create null rectangle metafile d_rect.left = 0; d_rect.top = 0; d_rect.right = 100*p->width()*GetDeviceCaps(metaDC, HORZSIZE)/(double)GetDeviceCaps(metaDC, HORZRES); d_rect.bottom = 100*p->height()*GetDeviceCaps(metaDC, VERTSIZE)/(double)GetDeviceCaps(metaDC, VERTRES); end();//delete the dummy metafile metaDC = CreateEnhMetaFileA(dc, fname.toLocal8Bit().data(), &d_rect, description); SetWindowExtEx(metaDC, p->width(), p->height(), 0); SetViewportExtEx(metaDC, p->width(), p->height(), 0); return true; } bool EmfPaintEngine::end() { HENHMETAFILE metafile = CloseEnhMetaFile( metaDC ); DeleteEnhMetaFile( metafile ); DeleteDC( metaDC ); return true; } void EmfPaintEngine::drawPoints ( const QPointF * points, int pointCount ) { setClipping(); QColor color = painter()->pen().color(); HBRUSH wbrush = CreateSolidBrush(RGB(color.red(), color.green(), color.blue())); SelectObject(metaDC, wbrush); int lw = painter()->pen().width(); QMatrix m = painter()->worldMatrix(); for (int i = 0; i < pointCount; i++){ QPointF p = m.map(points[i]); int x = qRound(p.x()); int y = qRound(p.y()); Rectangle(metaDC, x, y, x + lw, y + lw); } resetClipping(); DeleteObject(wbrush); } void EmfPaintEngine::drawLines ( const QLineF * lines, int lineCount ) { setClipping(); HPEN wpen = convertPen(painter()->pen()); SelectObject(metaDC, wpen); QMatrix m = painter()->worldMatrix(); for (int i = 0; i < lineCount; i++) { POINT *pts = new POINT[2]; QPointF p1 = m.map(lines[i].p1()); QPointF p2 = m.map(lines[i].p2()); pts[0].x = qRound(p1.x()); pts[0].y = qRound(p1.y()); pts[1].x = qRound(p2.x()); pts[1].y = qRound(p2.y()); Polyline(metaDC, pts, 2); delete [] pts; } resetClipping(); DeleteObject(wpen); } void EmfPaintEngine::drawPolygon ( const QPointF * points, int pointCount, PolygonDrawMode mode ) { setClipping(); HPEN wpen = convertPen(painter()->pen()); SelectObject(metaDC, wpen); HBRUSH wbrush = convertBrush(painter()->brush()); SelectObject(metaDC, wbrush); POINT *pts = new POINT[pointCount]; QMatrix m = painter()->worldMatrix(); for (int i = 0; i < pointCount; i++){ QPointF p = m.map (points[i]); pts[i].x = qRound(p.x()); pts[i].y = qRound(p.y()); } if (mode == QPaintEngine::PolylineMode) Polyline(metaDC, pts, pointCount); else if (mode == QPaintEngine::OddEvenMode) Polygon(metaDC, pts, pointCount); else qWarning("EmfEngine: drawPolygon with unsupported mode.\n"); resetClipping(); delete [] pts; DeleteObject(wpen); DeleteObject(wbrush); } void EmfPaintEngine::drawTextItem ( const QPointF & p, const QTextItem & textItem ) { setClipping(); SetBkMode( metaDC, TRANSPARENT ); QFont f = textItem.font(); QFontMetrics fm(f); HFONT wfont = CreateFontA(fm.height() - 1, fm.averageCharWidth(), 0, 0, 10*f.weight(), f.italic(), f.underline (), f.strikeOut(), DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, f.family().toAscii().data()); SelectObject( metaDC, wfont); QColor colour = painter()->pen().color(); SetTextColor( metaDC, RGB(colour.red(), colour.green(), colour.blue())); QString text = textItem.text(); int size = text.size(); QMatrix m = painter()->worldMatrix(); XFORM xf; xf.eM11 = m.m11(); xf.eM12 = m.m12(); xf.eM21 = m.m21(); xf.eM22 = m.m22(); xf.eDx = m.dx(); xf.eDy = m.dy(); SetWorldTransform(metaDC, &xf); #ifdef Q_WS_WIN wchar_t *wtext = (wchar_t *)malloc(size*sizeof(wchar_t)); if (!wtext){ qWarning("EmfEngine: Not enough memory in drawTextItem()."); return; } size = text.toWCharArray(wtext); TextOutW(metaDC, qRound(p.x()), qRound(p.y() - 0.85*fm.height()), wtext, size); free(wtext); #else TextOutA(metaDC, qRound(p.x()), qRound(p.y() - 0.85*fm.height()), text.toLocal8Bit().data(), size); #endif xf.eM11 = 1.0; xf.eM12 = 0.0; xf.eM21 = 0.0; xf.eM22 = 1.0; xf.eDx = 0.0; xf.eDy = 0.0; SetWorldTransform(metaDC, &xf); resetClipping(); DeleteObject(wfont); } void EmfPaintEngine::drawRects ( const QRectF * rects, int rectCount ) { setClipping(); HPEN wpen = convertPen(painter()->pen()); SelectObject(metaDC, wpen); HBRUSH wbrush = convertBrush(painter()->brush()); SelectObject(metaDC, wbrush); QMatrix m = painter()->worldMatrix(); for (int i = 0; i < rectCount; i++){ QRectF r = m.mapRect(rects[i]); Rectangle(metaDC, qRound(r.left()), qRound(r.top()), qRound(r.right()), qRound(r.bottom())); } resetClipping(); DeleteObject(wpen); DeleteObject(wbrush); } void EmfPaintEngine::drawEllipse ( const QRectF & rect ) { setClipping(); HPEN wpen = convertPen(painter()->pen()); SelectObject(metaDC, wpen); HBRUSH wbrush = convertBrush(painter()->brush()); SelectObject(metaDC, wbrush); QRectF r = painter()->worldMatrix().mapRect(rect); Ellipse(metaDC, qRound(r.left()), qRound(r.top()), qRound(r.right()), qRound(r.bottom())); resetClipping(); DeleteObject(wpen); DeleteObject(wbrush); } void EmfPaintEngine::drawPath ( const QPainterPath & path ) { setClipping(); int points = path.elementCount(); POINT *pts = new POINT[points]; BYTE *types = new BYTE[points]; POINT *bzs = new POINT[3]; int bez = 0; BeginPath(metaDC); QMatrix m = painter()->worldMatrix(); for (int i = 0; i < points; i++){ QPainterPath::Element el = path.elementAt(i); QPointF p = m.map(QPointF(el.x, el.y)); int x = qRound(p.x()); int y = qRound(p.y()); pts[i].x = x; pts[i].y = y; switch(el.type){ case QPainterPath::MoveToElement: types[i] = PT_MOVETO; #ifndef Q_WS_WIN MoveToEx (metaDC, x, y, 0); #endif break; case QPainterPath::LineToElement: types[i] = PT_LINETO; #ifndef Q_WS_WIN LineTo(metaDC, x, y); #endif break; case QPainterPath::CurveToElement: types[i] = PT_BEZIERTO; #ifndef Q_WS_WIN bzs[bez] = pts[i]; bez++; #endif break; case QPainterPath::CurveToDataElement: types[i] = PT_BEZIERTO; #ifndef Q_WS_WIN bzs[bez] = pts[i]; if (bez == 2){ PolyBezierTo(metaDC, bzs, 3); bez = 0; } else bez++; #endif break; } } HPEN wpen = convertPen(painter()->pen()); SelectObject(metaDC, wpen); #ifdef Q_WS_WIN PolyDraw(metaDC, pts, types, points); #else StrokePath(metaDC); #endif HBRUSH wbrush = convertBrush(painter()->brush()); SelectObject(metaDC, wbrush); EndPath(metaDC); if(QPoint(pts[0].x, pts[0].y) == QPoint(pts[points - 1].x, pts[points - 1].y)) StrokeAndFillPath(metaDC); else { FillPath(metaDC); #ifdef Q_WS_WIN PolyDraw(metaDC, pts, types, points); #else StrokePath(metaDC); #endif } resetClipping(); DeleteObject(wbrush); DeleteObject(wpen); delete [] pts; delete [] types; } void EmfPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &) { setClipping(); QMatrix m = painter()->worldMatrix(); QPointF p = m.map(r.topLeft()); int x = qRound(p.x()); int y = qRound(p.y()); int width = qRound(r.width()); int height = qRound(r.height()); #ifdef Q_WS_WIN HBITMAP hbtmp = nullptr; DWORD op = SRCCOPY; if (pm.hasAlpha()){ QImage image = pm.scaled(width, height).toImage(); image.invertPixels(); hbtmp = QPixmap::fromImage (image).toWinHBITMAP(); op = SRCINVERT; } else hbtmp = pm.scaled(width, height).toWinHBITMAP(); HDC hDC = CreateCompatibleDC(metaDC); SelectObject(hDC, hbtmp); BitBlt(metaDC, x, y, width, height, hDC, 0, 0, op); DeleteObject(hbtmp); DeleteDC(hDC); #else QImage image = pm.scaled(width, height).toImage(); for (int i = 0; i < width; i++){ for (int j = 0; j < height; j++){ QRgb rgb = image.pixel(i, j); if (qAlpha(rgb) == 255) SetPixel(metaDC, x + i, y + j, RGB(qRed(rgb), qGreen(rgb), qBlue(rgb))); } } #endif resetClipping(); } void EmfPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap & pix, const QPointF &) { setClipping(); #ifdef Q_WS_WIN HBITMAP hBmp = pix.toWinHBITMAP(); HBRUSH wbrush = CreatePatternBrush(hBmp); QMatrix m = painter()->worldMatrix(); QRectF dr = m.mapRect(r); RECT rect; rect.left = qRound(dr.left()); rect.top = qRound(dr.top()); rect.right = qRound(dr.right()); rect.bottom = qRound(dr.bottom()); FillRect(metaDC, &rect, wbrush); DeleteObject(hBmp); DeleteObject(wbrush); #else int width = qRound(r.width()); int height = qRound(r.height()); QPixmap pixmap(width, height); QPainter p(&pixmap); p.drawTiledPixmap(0, 0, width, height, pix); p.end(); drawPixmap(r, pixmap, QRectF()); #endif resetClipping(); } void EmfPaintEngine::drawImage(const QRectF & r, const QImage & image, const QRectF &, Qt::ImageConversionFlags flags) { QMatrix m = painter()->worldMatrix(); QPointF p = m.map(r.topLeft()); int x = qRound(p.x()); int y = qRound(p.y()); int width = qRound(r.width()); int height = qRound(r.height()); #ifdef Q_WS_WIN setClipping(); QPixmap pix = QPixmap::fromImage (image.scaled(width, height), flags); HBITMAP hbtmp = pix.toWinHBITMAP(); HDC hDC = CreateCompatibleDC(metaDC); SelectObject(hDC, hbtmp); BitBlt(metaDC, x, y, width, height, hDC, 0, 0, SRCCOPY); DeleteObject(hbtmp); DeleteDC(hDC); resetClipping(); #else QImage imag = image.scaled(width, height); for (int i = 0; i < width; i++){ for (int j = 0; j < height; j++){ QRgb rgb = imag.pixel(i, j); if (qAlpha(rgb) == 255) SetPixel(metaDC, x + i, y + j, RGB(qRed(rgb), qGreen(rgb), qBlue(rgb))); } } #endif } void EmfPaintEngine::setClipping() { #ifdef Q_WS_WIN if (painter()->hasClipping()) { QRect rect = painter()->clipRegion().boundingRect(); HRGN hrgn = CreateRectRgn(rect.left(), rect.top(), rect.right(), rect.bottom()); SelectClipRgn(metaDC, hrgn); DeleteObject(hrgn); } #endif } void EmfPaintEngine::resetClipping() { #ifdef Q_WS_WIN if (painter()->hasClipping()) SelectClipRgn(metaDC, nullptr); #endif } HPEN EmfPaintEngine::convertPen(const QPen& pen) { INT style = PS_nullptr; switch (pen.style()){ case Qt::SolidLine: style = PS_SOLID; break; case Qt::DashLine: style = PS_DASH; break; case Qt::DotLine: style = PS_DOT; break; case Qt::DashDotLine: style = PS_DASHDOT; break; case Qt::DashDotDotLine: style = PS_DASHDOTDOT; break; default: break; } INT capStyle = PS_ENDCAP_FLAT; switch (pen.capStyle()){ case Qt::FlatCap: break; case Qt::SquareCap: capStyle = PS_ENDCAP_SQUARE; break; case Qt::RoundCap: capStyle = PS_ENDCAP_ROUND; break; default: break; } INT joinStyle = PS_JOIN_MITER; switch (pen.joinStyle()){ case Qt::MiterJoin: break; case Qt::BevelJoin: joinStyle = PS_JOIN_BEVEL; break; case Qt::RoundJoin: joinStyle = PS_JOIN_ROUND; break; case Qt::SvgMiterJoin: joinStyle = PS_JOIN_MITER; break; default: break; } LOGBRUSH lbrush = {BS_SOLID, RGB(pen.color().red(),pen.color().green(),pen.color().blue()), 0}; return ExtCreatePen(PS_GEOMETRIC | style | capStyle | joinStyle, pen.width(), &lbrush, 0, nullptr); } HBRUSH EmfPaintEngine::convertBrush(const QBrush& brush) { LOGBRUSH lbrush = {BS_nullptr, 0, 0}; if (!brush.color().alpha()) return CreateBrushIndirect( &lbrush ); if (brush.color().alpha() < 255){//semi-transparent brush color qWarning ("Semi-transparent brushes are not supported by EmfEngine."); #ifdef Q_WS_WIN QPixmap pix(4, 4); pix.fill(Qt::white); QPainter p; p.begin(&pix); p.setPen(QPen(Qt::NoPen)); p.setBrush(brush); p.drawRect(QRect(0, 0, 4, 4)); p.end(); HBITMAP hBmp = pix.toWinHBITMAP(); HBRUSH wbrush = CreatePatternBrush(hBmp); DeleteObject(hBmp); return wbrush; #endif } LONG lbHatch = HS_HORIZONTAL; UINT lbStyle = BS_HATCHED; switch(brush.style()){ case Qt::NoBrush: lbStyle = BS_nullptr; break; case Qt::SolidPattern: lbStyle = BS_SOLID; break; case Qt::Dense1Pattern: case Qt::Dense2Pattern: case Qt::Dense3Pattern: case Qt::Dense4Pattern: case Qt::Dense5Pattern: case Qt::Dense6Pattern: case Qt::Dense7Pattern: { #ifdef Q_WS_WIN QPixmap pix(4, 4); pix.fill(Qt::white); QPainter p; p.begin(&pix); p.setPen(QPen(Qt::NoPen)); p.setBrush(brush); p.drawRect(QRect(0, 0, 4, 4)); p.end(); HBITMAP hbm = pix.toWinHBITMAP(); HBRUSH wbrush = CreatePatternBrush(hbm); DeleteObject(hbm); return wbrush; #endif } break; case Qt::HorPattern: break; case Qt::VerPattern: lbHatch = HS_VERTICAL; break; case Qt::CrossPattern: lbHatch = HS_CROSS; break; case Qt::BDiagPattern: lbHatch = HS_BDIAGONAL; break; case Qt::FDiagPattern: lbHatch = HS_FDIAGONAL; break; case Qt::DiagCrossPattern: lbHatch = HS_DIAGCROSS; break; case Qt::LinearGradientPattern: { qWarning("EmfEngine: Qt::LinearGradientPattern is not supported."); return CreateBrushIndirect( &lbrush ); } break; case Qt::ConicalGradientPattern: { qWarning("EmfEngine: Qt::ConicalGradientPattern is not supported."); return CreateBrushIndirect( &lbrush ); } break; case Qt::RadialGradientPattern: { qWarning("EmfEngine: Qt::RadialGradientPattern is not supported."); return CreateBrushIndirect( &lbrush ); } case Qt::TexturePattern: #ifdef Q_WS_WIN { HBITMAP hbm = brush.texture().toWinHBITMAP(); HBRUSH wbrush = CreatePatternBrush(hbm); DeleteObject(hbm); return wbrush; } #endif break; default: lbStyle = BS_SOLID; break; } LOGBRUSH logbrush = {lbStyle, RGB(brush.color().red(), brush.color().green(), brush.color().blue()), lbHatch}; return CreateBrushIndirect( &logbrush ); }