[TD]fix regression of svg hatch during export

- last worked in v0.20
- svg hatch tile field is not cropped on export to svg.  svg tiles are
  replaced by pixmap tiles for export.
This commit is contained in:
wandererfan
2024-04-10 17:28:32 -04:00
committed by WandererFan
parent afaf0ce8ea
commit cea3370a24
4 changed files with 219 additions and 106 deletions

View File

@@ -43,21 +43,23 @@
#include <QByteArrayMatcher>
#include "QGCustomRect.h"
#include "QGCustomSvg.h"
#include "QGCustomImage.h"
#include "QGICMark.h"
#include "QGIPrimPath.h"
#include "QGSPage.h"
#include "Rez.h"
#include "ZVALUE.h"
using namespace TechDrawGui;
using namespace TechDraw;
using DU = DrawUtil;
QGIFace::QGIFace(int index) :
projIndex(index),
m_hideSvgTiles(false),
projIndex(index),
m_hatchRotation(0.0)
{
m_segCount = 0;
// setFillMode(NoFill);
isHatched(false);
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
@@ -68,26 +70,27 @@ QGIFace::QGIFace(int index) :
m_pen.setStyle(m_styleCurrent);
setLineWeight(0.0); //0 = cosmetic
setPrettyNormal();
m_texture = QPixmap(); //empty texture
m_svgHatchArea = new QGCustomRect();
m_svgHatchArea->setParentItem(this);
m_svgCol = SVGCOLDEFAULT;
m_fillScale = 1.0;
m_imageSvgHatchArea = new QGCustomImage();
m_imageSvgHatchArea->setParentItem(this);
getParameters();
// set up style & colour defaults
m_colDefFill = App::Color(static_cast<uint32_t>(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", 0xFFFFFF)))
m_colDefFill = App::Color(static_cast<uint32_t>(Preferences::getPreferenceGroup("Colors")->GetUnsigned("FaceColor", COLWHITE)))
.asValue<QColor>();
m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? 0 : 255);
m_colDefFill.setAlpha(Preferences::getPreferenceGroup("Colors")->GetBool("ClearFace", false) ? ALPHALOW : ALPHAHIGH);
m_fillDef = Qt::SolidPattern;
m_fillSelect = Qt::SolidPattern;
setFillMode(m_colDefFill.alpha() ? PlainFill : NoFill);
setFillMode(NoFill);
if (m_colDefFill.alpha() > 0) {
setFillMode(PlainFill);
}
setFill(m_colDefFill, m_fillDef);
m_sharedRender = new QSvgRenderer();
@@ -106,6 +109,9 @@ void QGIFace::draw()
// Base::Console().Message("QGIF::draw - pen style: %d\n", m_pen.style());
setPath(m_outline); //Face boundary
m_svgHatchArea->hide();
m_imageSvgHatchArea->hide();
if (isHatched()) {
if (m_mode == GeomHatchFill) {
//GeomHatch does not appear in pdf if clipping is set to true
@@ -118,29 +124,28 @@ void QGIFace::draw()
lineSetToFillItems(ls);
}
}
m_svgHatchArea->hide();
} else if (m_mode == SvgFill) {
m_brush.setTexture(QPixmap());
m_fillNormal = m_fillDef;
m_fillStyleCurrent = m_fillNormal;
setFlag(QGraphicsItem::ItemClipsChildrenToShape,true);
loadSvgHatch(m_fileSpec);
//SVG tiles need to be clipped
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
buildSvgHatch();
m_svgHatchArea->show();
if (exporting()) {
buildPixHatch();
m_imageSvgHatchArea->show();
} else {
buildSvgHatch();
m_svgHatchArea->show();
}
} else if (m_mode == BitmapFill) {
m_fillStyleCurrent = Qt::TexturePattern;
m_texture = textureFromBitmap(m_fileSpec);
m_brush.setTexture(m_texture);
m_svgHatchArea->hide();
} else if (m_mode == PlainFill) {
setFill(m_colNormalFill, m_fillNormal);
m_svgHatchArea->hide();
}
} else {
// face is not hatched
m_svgHatchArea->hide();
}
show();
}
@@ -174,9 +179,9 @@ void QGIFace::setPrettySel() {
}
/// show or hide the edges of this face. Usually just for debugging
void QGIFace::setDrawEdges(bool b) {
void QGIFace::setDrawEdges(bool state) {
// Base::Console().Message("QGIF::setDrawEdges(%d)\n", b);
if (b) {
if (state) {
setStyle(Qt::DashLine);
} else {
setStyle(Qt::NoPen); //don't draw face lines, just fill
@@ -192,12 +197,12 @@ void QGIFace::setHatchFile(std::string fileSpec)
void QGIFace::loadSvgHatch(std::string fileSpec)
{
QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size()));
QFile f(qfs);
if (!f.open(QFile::ReadOnly | QFile::Text)) {
QFile file(qfs);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str());
return;
}
m_svgXML = f.readAll();
m_svgXML = file.readAll();
// search in the file for the "stroke" specification in order to find out what declaration style is used
// this is necessary to apply a color set by the user to the SVG
@@ -211,9 +216,9 @@ void QGIFace::loadSvgHatch(std::string fileSpec)
}
}
void QGIFace::setFillMode(QGIFace::fillMode m)
void QGIFace::setFillMode(QGIFace::fillMode mode)
{
m_mode = m;
m_mode = mode;
if ((m_mode == NoFill) ||
(m_mode == PlainFill)) {
isHatched(false);
@@ -265,13 +270,13 @@ QPen QGIFace::setGeomPen()
double QGIFace::getXForm()
{
//try to keep the pattern the same when View scales
auto s = scene();
if (s) {
auto vs = s->views(); //ptrs to views
if (!vs.empty()) {
auto v = vs.at(0);
auto i = v->transform().inverted();
return i.m11();
auto ourScene = scene();
if (ourScene) {
auto viewsAll = ourScene->views(); //ptrs to views
if (!viewsAll.empty()) {
auto view = viewsAll.at(0);
auto iView = view->transform().inverted();
return iView.m11();
}
}
return 1.0;
@@ -280,15 +285,15 @@ double QGIFace::getXForm()
/// remove the children that make up a PAT fill
void QGIFace::clearFillItems()
{
for (auto& f: m_fillItems) {
f->setParentItem(nullptr);
this->scene()->removeItem(f);
delete f;
for (auto& fill: m_fillItems) {
fill->setParentItem(nullptr);
this->scene()->removeItem(fill);
delete fill;
}
}
/// debugging tool draws a mark at a position on this face
void QGIFace::makeMark(double x, double y)
void QGIFace::makeMark(double x, double y) // NOLINT readability-identifier-length
{
QGICMark* cmItem = new QGICMark(-1);
cmItem->setParentItem(this);
@@ -315,9 +320,9 @@ void QGIFace::buildSvgHatch()
double overlayHeight = tilesHigh * hTile;
m_svgHatchArea->setRect(0., 0., overlayWidth,-overlayHeight);
m_svgHatchArea->centerAt(faceCenter);
QByteArray before, after;
before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
QByteArray colorXML = m_svgXML.replace(before, after);
if (!m_sharedRender->load(colorXML)) {
Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n");
@@ -352,56 +357,138 @@ void QGIFace::clearSvg()
hideSvg(true);
}
//this isn't used currently
QPixmap QGIFace::textureFromSvg(std::string fileSpec)
//! similar to svg hatch, but using pixmaps. we do this because QGraphicsSvgItems are not clipped
//! when we export the scene to svg, but pixmaps are clipped.
void QGIFace::buildPixHatch()
{
QString qs(QString::fromStdString(fileSpec));
QFileInfo ffi(qs);
if (!ffi.isReadable()) {
return QPixmap();
double wTile = SVGSIZEW * m_fillScale;
double hTile = SVGSIZEH * m_fillScale;
double faceWidth = m_outline.boundingRect().width();
double faceHeight = m_outline.boundingRect().height();
double faceOverlaySize = Preferences::svgHatchFactor() * std::max(faceWidth, faceHeight);
QPointF faceCenter = m_outline.boundingRect().center();
double tilesWide = ceil(faceOverlaySize / wTile);
double tilesHigh = ceil(faceOverlaySize / hTile);
double overlayWidth = tilesWide * wTile;
double overlayHeight = tilesHigh * hTile;
// handle color by brute force find & replace
QByteArray before = QString::fromStdString(SVGCOLPREFIX + SVGCOLDEFAULT).toUtf8();
QByteArray after = QString::fromStdString(SVGCOLPREFIX + m_svgCol).toUtf8();
QByteArray colorXML = m_svgXML.replace(before,after);
// TODO: there is a lot of switching back and forth between svg, QPixmap and QImage here that I
// don't really understand.
// render svg tile onto a QImage
if (!m_sharedRender->load(colorXML)) {
Base::Console().Message("QGIF::buildSvgHatch - failed to load svg string\n");
return;
}
QSvgRenderer renderer(qs);
QPixmap pixMap(renderer.defaultSize());
pixMap.fill(Qt::white); //try Qt::transparent?
QPainter painter(&pixMap);
renderer.render(&painter); //svg texture -> bitmap
return pixMap.scaled(m_fillScale, m_fillScale);
QImage svgImage(SVGSIZEW, SVGSIZEH, QImage::Format_ARGB32);
svgImage.fill(Qt::transparent);
QPainter painter(&svgImage);
if (svgImage.isNull()) {
Base::Console().Error("QGIF::buildPixHatch - svgImage is null\n");
return;
}
m_sharedRender->render(&painter);
// convert the QImage into a QPixmap
QPixmap tilePixmap(SVGSIZEW, SVGSIZEH);
tilePixmap = QPixmap::fromImage(svgImage);
tilePixmap = tilePixmap.scaled(wTile, hTile);
if (tilePixmap.isNull()) {
Base::Console().Error("QGIF::buildPixHatch - tilePixmap is null\n");
return;
}
// layout a field of bitmap tiles big enough to cover this face onto a Qimage
QImage tileField(overlayWidth, overlayHeight, QImage::Format_ARGB32);
QPointF fieldCenter(overlayWidth / 2.0, overlayHeight / 2.0);
tileField.fill(Qt::transparent);
QPainter painter2(&tileField);
QPainter::RenderHints hints = painter2.renderHints();
hints = hints & QPainter::Antialiasing;
painter2.setRenderHints(hints);
QPainterPath clipper = path();
QPointF offset = (fieldCenter - faceCenter);
clipper.translate(offset);
painter2.setClipPath(clipper);
long int tileCount = 0;
for (int iw = 0; iw < int(tilesWide); iw++) {
for (int ih = 0; ih < int(tilesHigh); ih++) {
painter2.drawPixmap(QRectF(iw * wTile, ih * hTile, wTile, hTile), //target rect
tilePixmap,
QRectF(0, 0, wTile, hTile)); //source rect
tileCount++;
if (tileCount > m_maxTile) {
Base::Console().Warning("Pixmap tile count exceeded: %ld\n",tileCount);
break;
}
}
if (tileCount > m_maxTile) {
break;
}
}
// turn the QImage field into a pixmap
QPixmap fieldPixmap(overlayWidth, overlayHeight);
fieldPixmap = QPixmap::fromImage(tileField);
// TODO: figure out how to rotate the pixmap without it looking terrible - far worse than the unrotated pixmap. svg hatch exported to svg will not be shown rotated
// QTransform xFormPixmap;
// xFormPixmap.rotate(m_hatchRotation);
// xFormPixmap.translate(getHatchOffset().x, getHatchOffset().y);
// m_imageSvgHatchArea->load(fieldPixmap.transformed(xFormPixmap));
QPixmap nothing;
m_imageSvgHatchArea->setPixmap(nothing);
m_imageSvgHatchArea->load(fieldPixmap);
m_imageSvgHatchArea->centerAt(faceCenter);
}
void QGIFace::setHatchColor(App::Color c)
void QGIFace::setHatchColor(App::Color color)
{
m_svgCol = c.asHexString();
m_geomColor = c.asValue<QColor>();
m_svgCol = color.asHexString();
m_geomColor = color.asValue<QColor>();
}
void QGIFace::setHatchScale(double s)
void QGIFace::setHatchScale(double scale)
{
m_fillScale = s;
m_fillScale = scale;
}
/// turn svg tiles on or off. QtSvg does not handle clipping,
/// so we must be able to turn the hatching on/off when exporting a face with an
/// svg hatch. Otherwise the full tile pattern is shown in the export.
/// NOTE: there appears to have been a change in Qt that it now clips svg items
void QGIFace::hideSvg(bool b)
void QGIFace::hideSvg(bool state)
{
m_hideSvgTiles = b;
m_hideSvgTiles = state;
}
/// create a QPixmap from a bitmap file. The QPixmap will be used as a QBrush
/// texture.
QPixmap QGIFace::textureFromBitmap(std::string fileSpec)
QPixmap QGIFace::textureFromBitmap(std::string fileSpec) const
{
QPixmap pix;
QString qfs(QString::fromUtf8(fileSpec.data(), fileSpec.size()));
QFile f(qfs);
if (!f.open(QFile::ReadOnly)) {
QFile file(qfs);
if (!file.open(QFile::ReadOnly)) {
Base::Console().Error("QGIFace could not read %s\n", fileSpec.c_str());
return pix;
}
QByteArray bytes = f.readAll();
QByteArray bytes = file.readAll();
pix.loadFromData(bytes);
if (m_hatchRotation != 0.0) {
QTransform rotator;
@@ -411,14 +498,14 @@ QPixmap QGIFace::textureFromBitmap(std::string fileSpec)
return pix;
}
void QGIFace::setLineWeight(double w) {
m_geomWeight = w;
void QGIFace::setLineWeight(double weight) {
m_geomWeight = weight;
}
void QGIFace::getParameters()
{
m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", 10000l);
m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", 10000l);
m_maxSeg = Preferences::getPreferenceGroup("PAT")->GetInt("MaxSeg", MAXSEGMENT);
m_maxTile = Preferences::getPreferenceGroup("Decorations")->GetInt("MaxSVGTile", MAXTILES);
}
QRectF QGIFace::boundingRect() const
@@ -430,3 +517,13 @@ QPainterPath QGIFace::shape() const
{
return path();
}
bool QGIFace::exporting() const
{
auto tdScene = dynamic_cast<QGSPage*>(scene());
if (!tdScene) {
return false;
}
return tdScene->getExportingSvg();
}