diff --git a/src/meshlabplugins/filter_texture/filter_texture.cpp b/src/meshlabplugins/filter_texture/filter_texture.cpp index 2387353a5..fabe394c3 100644 --- a/src/meshlabplugins/filter_texture/filter_texture.cpp +++ b/src/meshlabplugins/filter_texture/filter_texture.cpp @@ -35,6 +35,7 @@ #include "filter_texture.h" #include "pushpull.h" +#include "rastering.h" FilterTexturePlugin::FilterTexturePlugin() @@ -43,7 +44,8 @@ FilterTexturePlugin::FilterTexturePlugin() << FP_UV_WEDGE_TO_VERTEX << FP_BASIC_TRIANGLE_MAPPING << FP_SET_TEXTURE - << FP_COLOR_TO_TEXTURE; + << FP_COLOR_TO_TEXTURE + << FP_MESH_TEXCOLOR_TRANSFER; foreach(FilterIDType tt , types()) actionList << new QAction(filterName(tt), this); @@ -56,7 +58,8 @@ QString FilterTexturePlugin::filterName(FilterIDType filterId) const case FP_UV_WEDGE_TO_VERTEX : return QString("Convert PerWedge UV into PerVertex UV"); case FP_BASIC_TRIANGLE_MAPPING : return QString("Basic Triangle Mapping"); case FP_SET_TEXTURE : return QString("Set Texture"); - case FP_COLOR_TO_TEXTURE : return QString("Vertex Color to Texture transfer"); + case FP_COLOR_TO_TEXTURE : return QString("Vertex Color to Texture"); + case FP_MESH_TEXCOLOR_TRANSFER : return QString("VertexColor/Texture to Texture transfer"); default : assert(0); } } @@ -73,6 +76,7 @@ QString FilterTexturePlugin::filterInfo(FilterIDType filterId) const case FP_SET_TEXTURE : return QString("Set a texture associated with current mesh parametrization.
" "If the texture provided exists it will be simply set else a dummy one will be created in the same directory"); case FP_COLOR_TO_TEXTURE : return QString("Fills the specified texture accordingly to per vertex color"); + case FP_MESH_TEXCOLOR_TRANSFER : return QString("Transfer vertex color or texture color from one mesh to another's texture."); default : assert(0); } return QString("Unknown Filter"); @@ -87,6 +91,7 @@ int FilterTexturePlugin::getPreConditions(QAction *a) const case FP_BASIC_TRIANGLE_MAPPING : return MeshFilterInterface::FP_Face; case FP_SET_TEXTURE : return MeshFilterInterface::FP_WedgeTexCoord; case FP_COLOR_TO_TEXTURE : return MeshFilterInterface::FP_WedgeTexCoord + MeshFilterInterface::FP_VertexColor; + case FP_MESH_TEXCOLOR_TRANSFER : return MeshFilterInterface::FP_WedgeTexCoord; default: assert(0); } return MeshFilterInterface::FP_Generic; @@ -101,6 +106,7 @@ int FilterTexturePlugin::getRequirements(QAction *a) case FP_BASIC_TRIANGLE_MAPPING : case FP_SET_TEXTURE : return MeshModel::MM_NONE; case FP_COLOR_TO_TEXTURE : return MeshModel::MM_FACEFACETOPO; + case FP_MESH_TEXCOLOR_TRANSFER : return MeshModel::MM_NONE; default: assert(0); } return MeshModel::MM_NONE; @@ -115,6 +121,7 @@ int FilterTexturePlugin::postCondition( QAction *a) const case FP_BASIC_TRIANGLE_MAPPING : return MeshModel::MM_WEDGTEXCOORD; case FP_SET_TEXTURE : return MeshModel::MM_UNKNOWN; case FP_COLOR_TO_TEXTURE : return MeshModel::MM_UNKNOWN; + case FP_MESH_TEXCOLOR_TRANSFER : return MeshModel::MM_UNKNOWN; default: assert(0); } return MeshModel::MM_NONE; @@ -132,12 +139,28 @@ FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a) case FP_BASIC_TRIANGLE_MAPPING : case FP_SET_TEXTURE : case FP_COLOR_TO_TEXTURE : + case FP_MESH_TEXCOLOR_TRANSFER : return MeshFilterInterface::Texture; default : assert(0); } return MeshFilterInterface::Generic; } +static QString extractFilenameWOExt(MeshModel* mm) +{ + QString fileName(mm->fileName.c_str()); + int lastPoint = fileName.lastIndexOf("."); + if (lastPoint <= 0) + fileName.clear(); + else { + fileName = fileName.left(lastPoint); + lastPoint = std::max(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/')); + if (lastPoint > 0) + fileName = fileName.right(fileName.size() - 1 - lastPoint); + } + return fileName; +} + // This function define the needed parameters for each filter. Return true if the filter has some parameters // it is called every time, so you can set the default value of parameters according to the mesh // For each parmeter you need to define, @@ -145,7 +168,7 @@ FilterTexturePlugin::FilterClass FilterTexturePlugin::getClass(QAction *a) // - the string shown in the dialog // - the default value // - a possibly long string describing the meaning of that parameter (shown as a popup help in the dialog) -void FilterTexturePlugin::initParameterSet(QAction *action, MeshModel &m, RichParameterSet & parlst) +void FilterTexturePlugin::initParameterSet(QAction *action, MeshDocument &md, RichParameterSet & parlst) { switch(ID(action)) { case FP_UV_TO_COLOR : @@ -156,36 +179,18 @@ void FilterTexturePlugin::initParameterSet(QAction *action, MeshModel &m, RichPa case FP_BASIC_TRIANGLE_MAPPING : parlst.addParam(new RichInt("sidedim", 0, "Quads per line", "Indicates how many triangles have to be put on each line (every quad contains two triangles)\nLeave 0 for automatic calculation")); parlst.addParam(new RichInt("textdim", 1024, "Texture Dimension (px)", "Gives an indication on how big the texture is")); - parlst.addParam(new RichInt("border", 1, "Inter-Triangle border (px)", "Specifies how many pixels to be left between triangles in parametrization domain")); + parlst.addParam(new RichInt("border", 2, "Inter-Triangle border (px)", "Specifies how many pixels to be left between triangles in parametrization domain")); parlst.addParam(new RichEnum("method", 0, QStringList("Basic") << "Space-optimizing", "Method", "Choose space optimizing to map smaller faces into smaller triangles in parametrizazion domain")); break; case FP_SET_TEXTURE : { - QString fileName(m.fileName.c_str()); - int lastPoint = fileName.lastIndexOf("."); - if (lastPoint <= 0) - fileName = QString(""); - else { - fileName = fileName.left(lastPoint); - lastPoint = std::max(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/')); - if (lastPoint > 0) - fileName = fileName.right(fileName.size() - 1 - lastPoint); - } + QString fileName = extractFilenameWOExt(md.mm()); fileName = fileName.append(".png"); parlst.addParam(new RichString("textName", fileName, "Texture file", "If the file exists it will be associated to the mesh else a dummy one will be created")); parlst.addParam(new RichInt("textDim", 1024, "Texture Dimension (px)", "If the named texture doesn't exists the dummy one will be squared with this size")); } break; case FP_COLOR_TO_TEXTURE : { - QString fileName(m.fileName.c_str()); - int lastPoint = fileName.lastIndexOf("."); - if (lastPoint <= 0) - fileName = QString(""); - else { - fileName = fileName.left(lastPoint); - lastPoint = std::max(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/')); - if (lastPoint > 0) - fileName = fileName.right(fileName.size() - 1 - lastPoint); - } + QString fileName = extractFilenameWOExt(md.mm()); fileName = fileName.append("_color.png"); parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created")); parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width")); @@ -194,6 +199,24 @@ void FilterTexturePlugin::initParameterSet(QAction *action, MeshModel &m, RichPa parlst.addParam(new RichBool("assign", false, "Assign texture", "assign the newly created texture")); } break; + case FP_MESH_TEXCOLOR_TRANSFER : { + QString fileName = extractFilenameWOExt(md.mm()); + fileName = fileName.append("_color.png"); + parlst.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh", + "The mesh that contains the source data that we want to transfer")); + parlst.addParam(new RichEnum("data", 0, QStringList("Vertex Color") << "Texture Color", "Color Data Source", + "Choose to transfer color information from source mesh texture or vertex color")); + parlst.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh", + "The mesh whose texture will be filled according to source mesh texture or vertex color")); + parlst.addParam(new RichAbsPerc("upperBound", md.mm()->cm.bbox.Diag()/50.0, 0.0f, md.mm()->cm.bbox.Diag(), + tr("Max Dist Search"), tr("Sample points for which we do not find anything whithin this distance are rejected and not considered for recovering color"))); + parlst.addParam(new RichString("textName", fileName, "Texture file", "The texture file to be created")); + parlst.addParam(new RichInt("textW", 1024, "Texture width (px)", "The texture width")); + parlst.addParam(new RichInt("textH", 1024, "Texture height (px)", "The texture height")); + parlst.addParam(new RichBool("overwrite", false, "Overwrite Target Mesh Texture", "if target mesh has a texture will be overwritten (with provided texture dimension)")); + parlst.addParam(new RichBool("assign", false, "Assign Texture", "assign the newly created texture to target mesh")); + } + break; default : assert(0); } } @@ -272,41 +295,9 @@ inline void buildTrianglesCache(std::vector &arr, int maxLevels, float bor if (--maxLevels <= 0) return; buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+2); buildTrianglesCache (arr, maxLevels, border, quadSize, 2*idx+3); - } /////// "COLOR TO TEXTURE" FILTER NEEDED STUFF -class RasterSampler -{ -public: - RasterSampler(QImage &_img, int _th) : img(_img), texH(_th) {assert(texH>0);}; - - QImage &img; - int texH; - - void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp) - { - CMeshO::VertexType::ColorType c; - c.lerp(f.V(0)->cC(), f.V(1)->cC(), f.V(2)->cC(), p); - img.setPixel(tp.X(), texH - tp.Y(), qRgba(c[0], c[1], c[2], 255)); - } -}; - -template -static void TextureCorrected(MetroMesh & m, VertexSampler &ps, int textureWidth, int textureHeight) -{ - typedef typename MetroMesh::FaceIterator FaceIterator; - FaceIterator fi; - - printf("Similar Triangles face sampling\n"); - for(fi=m.face.begin(); fi != m.face.end(); fi++) - { - vcg::Point2f ti[3]; - for(int i=0;i<3;++i) - ti[i]=vcg::Point2f((*fi).WT(i).U() * textureWidth - 0.5, (*fi).WT(i).V() * textureHeight + 0.5); - vcg::tri::SurfaceSampling::SingleFaceRaster(*fi, ps, ti[0],ti[1],ti[2]); - } -} // ERROR CHECKING UTILITY #define CheckError(x,y); if ((x)) {this->errorMessage = (y); return false;} @@ -494,15 +485,17 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet int fidx = *it; int lEdge = getLongestEdge(m.cm.face[fidx]); Tri2 &t = cache[pos]; - tmp = t.P0(lEdge) + origin; - m.cm.face[fidx].WT(0) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); - m.cm.face[fidx].WT(0).N() = 0; - tmp = t.P1(lEdge) + origin; - m.cm.face[fidx].WT(1) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); - m.cm.face[fidx].WT(1).N() = 0; - tmp = t.P2(lEdge) + origin; - m.cm.face[fidx].WT(2) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); - m.cm.face[fidx].WT(2).N() = 0; + tmp = t.P(0) + origin; + m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); + m.cm.face[fidx].WT(lEdge).N() = 0; + lEdge = (lEdge+1)%3; + tmp = t.P(1) + origin; + m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); + m.cm.face[fidx].WT(lEdge).N() = 0; + lEdge = (lEdge+1)%3; + tmp = t.P(2) + origin; + m.cm.face[fidx].WT(lEdge) = CFaceO::TexCoordType(tmp.X(), tmp.Y()); + m.cm.face[fidx].WT(lEdge).N() = 0; ++it; cb(face*100/faceNo, "Generating parametrization..."); } @@ -600,18 +593,14 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet // Creates path to texture file QString fileName(m.fileName.c_str()); - int lastPoint = std::max(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/')); - if (lastPoint < 0) - fileName = textName; - else - fileName = fileName.left(lastPoint).append(QDir::separator()).append(textName); + fileName = fileName.left(std::max(fileName.lastIndexOf('\\'),fileName.lastIndexOf('/'))+1).append(textName); QFile textFile(fileName); if (!textFile.exists()) { - //create dummy checkers texture image + // Create dummy checkers texture image QImage img(textDim, textDim, QImage::Format_RGB32); - img.fill(Qt::white); + img.fill(qRgb(255,255,255)); // white QPainter p(&img); QBrush gray(Qt::gray); QRect rect(0,0,CHECKERDIM,CHECKERDIM); @@ -625,12 +614,14 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet p.fillRect(rect, gray); } } - //save + + // Save texture CheckError(!img.save(fileName, "PNG"), "Specified file cannot be saved"); Log(GLLogStream::FILTER, "Dummy Texture \"%s\" Created ", fileName.toStdString().c_str()); assert(textFile.exists()); } - //set + + //Assign texture m.cm.textures.clear(); m.cm.textures.push_back(textName.toStdString()); } @@ -644,7 +635,7 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet bool overwrite = par.getBool("overwrite"); bool assign = par.getBool("assign"); - CheckError(!QFile(m.fileName.c_str()).exists(), "Save the file before creting a texture"); + CheckError(!QFile(m.fileName.c_str()).exists(), "Save the file before creating a texture"); QString filePath(m.fileName.c_str()); filePath = filePath.left(std::max(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1); @@ -667,66 +658,159 @@ bool FilterTexturePlugin::applyFilter(QAction *filter, MeshModel &m, RichParamet // Image creation QImage img(QSize(textW,textH), QImage::Format_ARGB32); - img.fill(Qt::transparent); + img.fill(qRgba(0,0,0,0)); // transparent // Compute (texture-space) border edges vcg::tri::UpdateTopology::FaceFaceFromTexCoord(m.cm); vcg::tri::UpdateFlags::FaceBorderFromFF(m.cm); - - // Rasterizing safety buffer area along border edges - QPainter p(&img); - QGradientStops gs; - QLinearGradient grad; - QPen pen(Qt::SolidLine); - grad.setCoordinateMode(QGradient::LogicalMode); - pen.setCapStyle(Qt::RoundCap); - pen.setWidthF(2.0); - gs << QGradientStop(0.0,Qt::transparent) << QGradientStop(1.0,Qt::transparent); - - CMeshO::FaceIterator fi; - for (fi=m.cm.face.begin(); fi!=m.cm.face.end(); ++fi) - for(int i=0;i<3;++i) - if (fi->IsB(i)) - { - QPointF t[2]; - CMeshO::VertexType::ColorType c[2]; - t[0] = QPointF(fi->cWT(i).U() * textW - 0.5, (1.0 - fi->cWT(i).V()) * textH - 0.5); - t[1] = QPointF(fi->cWT((i+1)%3).U() * textW - 0.5, (1.0 - fi->cWT((i+1)%3).V()) * textH - 0.5); - c[0] = fi->V(i)->cC(); - c[1] = fi->V((i+1)%3)->cC(); - grad.setStart(t[0].x()+0.5, t[0].y()+0.5); - grad.setFinalStop(t[1].x()+0.5, t[1].y()+0.5); - gs[0].second = QColor(c[0][0],c[0][1], c[0][2]); - gs[1].second = QColor(c[1][0],c[1][1], c[1][2]); - grad.setStops(gs); - pen.setBrush(QBrush(grad)); - p.setPen(pen); - p.drawLine(t[0], t[1]); - } - + // Rasterizing triangles - RasterSampler rs(img, textH); + RasterSampler rs(img); + rs.InitCallback(cb, m.cm.fn, 0, 80); TextureCorrected(m.cm,rs,textW,textH); + // Revert alpha values from border edge pixel to 255 + cb(81, "Cleaning up texture ..."); + for (int y=0; y 0) + img.setPixel(x,y, px | 0xff000000); + } + // PullPush - vcg::PullPush(img, Qt::transparent); + cb(85, "Filling texture holes..."); + vcg::PullPush(img, qRgba(0,0,0,0)); // Undo topology changes vcg::tri::UpdateTopology::FaceFace(m.cm); vcg::tri::UpdateFlags::FaceBorderFromFF(m.cm); - // Save + // Save texture + cb(90, "Saving texture ..."); CheckError(!img.save(filePath), "Texture file cannot be saved"); Log(GLLogStream::FILTER, "Texture \"%s\" Created", filePath.toStdString().c_str()); + assert(QFile(filePath).exists()); - // Assign + // Assign texture if (assign && !overwrite) { m.cm.textures.clear(); m.cm.textures.push_back(textName.toStdString()); } + cb(100, "Done"); } break; + case FP_MESH_TEXCOLOR_TRANSFER : { + MeshModel *srcMesh = par.getMesh("sourceMesh"); + bool colorSampling; + switch (par.getEnum("data")) { + case 0: colorSampling = true; break; + case 1: colorSampling = false; break; + default: assert(0); + } + MeshModel *trgMesh = par.getMesh("targetMesh"); + float upperbound = par.getAbsPerc("upperBound"); // maximum distance to stop search + QString textName = par.getString("textName"); + int textW = par.getInt("textW"); + int textH = par.getInt("textH"); + bool overwrite = par.getBool("overwrite"); + bool assign = par.getBool("assign"); + + CheckError(!QFile(trgMesh->fileName.c_str()).exists(), "Save the target mesh before creating a texture"); + CheckError(trgMesh->cm.fn == 0 || trgMesh->cm.fn == 0, "Both meshes require to have faces"); + CheckError(!trgMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Target mesh doesn't have Per Wedge Texture Coordinates"); + + QImage srcImg; + + if (colorSampling) { + CheckError(!srcMesh->hasDataMask(MeshModel::MM_VERTCOLOR), "Source mesh doesn't have Per Vertex Color"); + } else { + CheckError(!srcMesh->hasDataMask(MeshModel::MM_WEDGTEXCOORD), "Source mesh doesn't have Per Wedge Texture Coordinates"); + CheckError(srcMesh->cm.textures.empty(), "Source mesh doesn't have any associated texture"); + QString path(srcMesh->fileName.c_str()); + path = path.left(std::max(path.lastIndexOf('\\'),path.lastIndexOf('/'))+1).append(srcMesh->cm.textures[0].c_str()); + CheckError(!QFile(path).exists(), QString("Source texture \"").append(path).append("\" doesn't exists")); + CheckError(!srcImg.load(path), QString("Source texture \"").append(path).append("\" cannot be opened")); + } + + QString filePath(trgMesh->fileName.c_str()); + filePath = filePath.left(std::max(filePath.lastIndexOf('\\'),filePath.lastIndexOf('/'))+1); + + if (!overwrite) + { + // Check textName and eventually add .png ext + CheckError(textName.length() == 0, "Texture file not specified"); + CheckError(std::max(textName.lastIndexOf("\\"),textName.lastIndexOf("/")) != -1, "Path in Texture file not allowed"); + if (!textName.endsWith(".png", Qt::CaseInsensitive)) + textName.append(".png"); + filePath.append(textName); + } else { + CheckError(trgMesh->cm.textures.empty(), "Mesh has no associated texture to overwrite"); + filePath.append(trgMesh->cm.textures[0].c_str()); + } + + CheckError(textW <= 0, "Texture Width has an incorrect value"); + CheckError(textH <= 0, "Texture Height has an incorrect value"); + + // Image creation + QImage img(QSize(textW,textH), QImage::Format_ARGB32); + img.fill(qRgba(0,0,0,0)); + + // Compute (texture-space) border edges + trgMesh->updateDataMask(MeshModel::MM_FACEFACETOPO); + vcg::tri::UpdateTopology::FaceFaceFromTexCoord(trgMesh->cm); + vcg::tri::UpdateFlags::FaceBorderFromFF(trgMesh->cm); + + // Rasterizing faces + srcMesh->updateDataMask(MeshModel::MM_FACEMARK); + vcg::tri::UpdateNormals::PerFaceNormalized(srcMesh->cm); + vcg::tri::UpdateFlags::FaceProjection(srcMesh->cm); + if (colorSampling) + { + TransferColorSampler sampler(srcMesh->cm, img, upperbound); // color sampling + sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80); + TextureCorrected(trgMesh->cm,sampler,img.width(),img.height()); + } else { + TransferColorSampler sampler(srcMesh->cm, img, &srcImg, upperbound); // texture sampling + sampler.InitCallback(cb, trgMesh->cm.fn, 0, 80); + TextureCorrected(trgMesh->cm,sampler,img.width(),img.height()); + } + + // Revert alpha values from border edge pixel to 255 + cb(81, "Cleaning up texture ..."); + for (int y=0; y 0) + img.setPixel(x,y, px | 0xff000000); + } + + // PullPush + cb(85, "Filling texture holes..."); + vcg::PullPush(img, qRgba(0,0,0,0)); + + // Undo topology changes + vcg::tri::UpdateTopology::FaceFace(trgMesh->cm); + vcg::tri::UpdateFlags::FaceBorderFromFF(trgMesh->cm); + + // Save texture + cb(90, "Saving texture ..."); + CheckError(!img.save(filePath), "Texture file cannot be saved"); + Log(GLLogStream::FILTER, "Texture \"%s\" Created", filePath.toStdString().c_str()); + assert(QFile(filePath).exists()); + + // Assign texture + if (assign && !overwrite) { + m.cm.textures.clear(); + m.cm.textures.push_back(textName.toStdString()); + } + cb(100, "Done"); + + } + break; default: assert(0); } return true; diff --git a/src/meshlabplugins/filter_texture/filter_texture.h b/src/meshlabplugins/filter_texture/filter_texture.h index 30a375ae7..4d948bbba 100644 --- a/src/meshlabplugins/filter_texture/filter_texture.h +++ b/src/meshlabplugins/filter_texture/filter_texture.h @@ -40,7 +40,8 @@ public: FP_UV_WEDGE_TO_VERTEX, FP_BASIC_TRIANGLE_MAPPING, FP_SET_TEXTURE, - FP_COLOR_TO_TEXTURE + FP_COLOR_TO_TEXTURE, + FP_MESH_TEXCOLOR_TRANSFER }; FilterTexturePlugin(); @@ -48,7 +49,7 @@ public: virtual QString filterName(FilterIDType filter) const; virtual QString filterInfo(FilterIDType filter) const; virtual bool autoDialog(QAction *) {return true;} - virtual void initParameterSet(QAction *,MeshModel &/*m*/, RichParameterSet & /*parent*/); + virtual void initParameterSet(QAction *,MeshDocument &/*m*/, RichParameterSet & /*parent*/); virtual bool applyFilter(QAction *filter, MeshModel &m, RichParameterSet & /*parent*/, vcg::CallBackPos * cb); virtual int getRequirements(QAction *); virtual int getPreConditions(QAction *) const; diff --git a/src/meshlabplugins/filter_texture/filter_texture.pro b/src/meshlabplugins/filter_texture/filter_texture.pro index c222c60de..ffb9f7455 100644 --- a/src/meshlabplugins/filter_texture/filter_texture.pro +++ b/src/meshlabplugins/filter_texture/filter_texture.pro @@ -1,7 +1,8 @@ include (../../sharedfilter.pri) HEADERS += filter_texture.h \ - pushpull.h + pushpull.h \ + rastering.h SOURCES += filter_texture.cpp diff --git a/src/meshlabplugins/filter_texture/rastering.h b/src/meshlabplugins/filter_texture/rastering.h new file mode 100644 index 000000000..cea659ada --- /dev/null +++ b/src/meshlabplugins/filter_texture/rastering.h @@ -0,0 +1,328 @@ +/**************************************************************************** +* MeshLab o o * +* A versatile mesh processing toolbox o o * +* _ O _ * +* Copyright(C) 2005 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* 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 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 (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ + +#ifndef _RASTERING_H +#define _RASTERING_H + +#include +#include +#include +#include + +class RasterSampler +{ + QImage &trgImg; + + // Callback stuff + vcg::CallBackPos *cb; + const CMeshO::FaceType *currFace; + int faceNo, faceCnt, start, offset; + +public: + RasterSampler(QImage &_img) : trgImg(_img) {} + + void InitCallback(vcg::CallBackPos *_cb, int _faceNo, int _start=0, int _offset=100) + { + assert(_faceNo > 0); + assert(_start>=0); + assert(_offset>=0 && _offset <= 100-_start); + cb = _cb; + faceNo = _faceNo; + faceCnt = 0; + start = _start; + offset = _offset; + currFace = NULL; + } + + // expects points outside face (face affecting) with baycentric coords = (bX, bY, -) + void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp) + { + CMeshO::VertexType::ColorType c; + CMeshO::CoordType bary = p; + int alpha = 255; + if (fabs(p[0]+p[1]+p[2]-1)>=0.00001) + if (p[0] <.0) {alpha = 254+p[0]*128; bary[0] = 0.;} else + if (p[1] <.0) {alpha = 254+p[1]*128; bary[1] = 0.;} else + if (p[2] <.0) {alpha = 254+p[2]*128; bary[2] = 0.;} + if (alpha==255 || qAlpha(trgImg.pixel(tp.X(), trgImg.height() - tp.Y())) < alpha) + { + c.lerp(f.V(0)->cC(), f.V(1)->cC(), f.V(2)->cC(), bary); + trgImg.setPixel(tp.X(), trgImg.height() - tp.Y(), qRgba(c[0], c[1], c[2], alpha)); + } + if (cb) + { + if (&f != currFace) {currFace = &f; ++faceCnt;} + cb(start + faceCnt*offset/faceNo, "Rasterizing faces ..."); + } + } +}; + +class TransferColorSampler +{ + typedef vcg::GridStaticPtr MetroMeshGrid; + + QImage &trgImg; + QImage *srcImg; + float dist_upper_bound; + bool fromTexture; + MetroMeshGrid unifGridFace; + + // Callback stuff + vcg::CallBackPos *cb; + const CMeshO::FaceType *currFace; + int faceNo, faceCnt, start, offset; + + typedef vcg::tri::FaceTmark MarkerFace; + MarkerFace markerFunctor; + +public: + TransferColorSampler(CMeshO &_srcMesh, QImage &_trgImg, float upperBound) + : trgImg(_trgImg), dist_upper_bound(upperBound) + { + unifGridFace.Set(_srcMesh.face.begin(),_srcMesh.face.end()); + markerFunctor.SetMesh(&_srcMesh); + fromTexture = false; + } + + TransferColorSampler(CMeshO &_srcMesh, QImage &_trgImg, QImage *_srcImg, float upperBound) + : trgImg(_trgImg), dist_upper_bound(upperBound) + { + assert(_srcImg != NULL); + srcImg = _srcImg; + unifGridFace.Set(_srcMesh.face.begin(),_srcMesh.face.end()); + markerFunctor.SetMesh(&_srcMesh); + fromTexture = true; + } + + void InitCallback(vcg::CallBackPos *_cb, int _faceNo, int _start=0, int _offset=100) + { + assert(_faceNo > 0); + assert(_start>=0); + assert(_offset>=0 && _offset <= 100-_start); + cb = _cb; + faceNo = _faceNo; + faceCnt = 0; + start = _start; + offset = _offset; + currFace = NULL; + } + // expects points outside face (face affecting) with baycentric coords = (bX, bY, -) + void AddTextureSample(const CMeshO::FaceType &f, const CMeshO::CoordType &p, const vcg::Point2i &tp) + { + // Calculate correct brycentric coords + CMeshO::CoordType bary = p; + int alpha = 255; + if (fabs(p[0]+p[1]+p[2]-1)>=0.00001) + if (p[0] <.0) {alpha = 254+p[0]*128; bary[0] = 0.;} else + if (p[1] <.0) {alpha = 254+p[1]*128; bary[1] = 0.;} else + if (p[2] <.0) {alpha = 254+p[2]*128; bary[2] = 0.;} + + // Get point on face + CMeshO::CoordType startPt; + startPt[0] = bary[0]*f.V(0)->P().X()+bary[1]*f.V(1)->P().X()+bary[2]*f.V(2)->P().X(); + startPt[1] = bary[0]*f.V(0)->P().Y()+bary[1]*f.V(1)->P().Y()+bary[2]*f.V(2)->P().Y(); + startPt[2] = bary[0]*f.V(0)->P().Z()+bary[1]*f.V(1)->P().Z()+bary[2]*f.V(2)->P().Z(); + + // Retrieve closest point on source mesh + CMeshO::CoordType closestPt; + vcg::face::PointDistanceBaseFunctor PDistFunct; + float dist=dist_upper_bound; + CMeshO::FaceType *nearestF; + nearestF = unifGridFace.GetClosest(PDistFunct, markerFunctor, startPt, dist_upper_bound, dist, closestPt); + if (dist == dist_upper_bound) return; + + // Convert point to barycentric coords + vcg::Point3f interp; + int axis = 0; + float tmp = -1; + for (int i=0; i<3; ++i) + if (fabs(nearestF->cN()[i]) > tmp) {tmp = fabs(nearestF->cN()[i]); axis = i;} + bool ret = InterpolationParameters(*nearestF, axis, closestPt, interp); + assert(ret); + interp[2]=1.0-interp[1]-interp[0]; + + if (fromTexture) + { + // NOT IMPLEMENTED YET + } + else + { + // Calculate and set color + if (alpha==255 || qAlpha(trgImg.pixel(tp.X(), trgImg.height() - tp.Y())) < alpha) + { + CMeshO::VertexType::ColorType c; + c.lerp(nearestF->V(0)->cC(), nearestF->V(1)->cC(), nearestF->V(2)->cC(), interp); + trgImg.setPixel(tp.X(), trgImg.height() - tp.Y(), qRgba(c[0], c[1], c[2], alpha)); + } + } + + if (cb) + { + if (&f != currFace) {currFace = &f; ++faceCnt;} + cb(start + faceCnt*offset/faceNo, "Rasterizing faces ..."); + } + } +}; + +template +static void SingleFaceRasterWEdge(typename MetroMesh::FaceType &f, VertexSampler &ps, + const vcg::Point2 & v0, + const vcg::Point2 & v1, + const vcg::Point2 & v2) +{ + typedef typename MetroMesh::ScalarType S; + // Calcolo bounding box + vcg::Box2i bbox; + + if(v0[0]int(v2[0])) bbox.min[0]=int(v2[0]); + else if(bbox.max[0]int(v2[1])) bbox.min[1]=int(v2[1]); + else if(bbox.max[1] d10 = v1 - v0; + vcg::Point2 d21 = v2 - v1; + vcg::Point2 d02 = v0 - v2; + + // Preparazione prodotti scalari + S b0 = (bbox.min[0]-v0[0])*d10[1] - (bbox.min[1]-v0[1])*d10[0]; + S b1 = (bbox.min[0]-v1[0])*d21[1] - (bbox.min[1]-v1[1])*d21[0]; + S b2 = (bbox.min[0]-v2[0])*d02[1] - (bbox.min[1]-v2[1])*d02[0]; + // Preparazione degli steps + S db0 = d10[1]; + S db1 = d21[1]; + S db2 = d02[1]; + // Preparazione segni + S dn0 = -d10[0]; + S dn1 = -d21[0]; + S dn2 = -d02[0]; + + //Calcolo orientamento + bool flipped = d02 * vcg::Point2(-d10[1], d10[0]) >= 0; + + // Precalcolo Calcolo edge di bordo + vcg::Segment2 borderEdges[3]; + S edgeLength[3]; + unsigned char edgeMask = 0; + if (f.IsB(0)) { + borderEdges[0] = vcg::Segment2(v0, v1); + edgeLength[0] = borderEdges[0].Length(); + edgeMask |= 1; + } + if (f.IsB(1)) { + borderEdges[1] = vcg::Segment2(v1, v2); + edgeLength[1] = borderEdges[1].Length(); + edgeMask |= 2; + } + if (f.IsB(2)) { + borderEdges[2] = vcg::Segment2(v2, v0); + edgeLength[2] = borderEdges[2].Length(); + edgeMask |= 4; + } + + // Rasterizzazione + double de = v0[0]*v1[1]-v0[0]*v2[1]-v1[0]*v0[1]+v1[0]*v2[1]-v2[0]*v1[1]+v2[0]*v0[1]; + + for(int x=bbox.min[0]-1;x<=bbox.max[0]+1;++x) + { + bool in = false; + S n[3] = { b0-db0-dn0, b1-db1-dn1, b2-db2-dn2}; + for(int y=bbox.min[1]-1;y<=bbox.max[1]+1;++y) + { + if((n[0]>=0 && n[1]>=0 && n[2]>=0) || (n[0]<=0 && n[1]<=0 && n[2]<=0)) + { + typename MetroMesh::CoordType baryCoord; + baryCoord[0] = double(-y*v1[0]+v2[0]*y+v1[1]*x-v2[0]*v1[1]+v1[0]*v2[1]-x*v2[1])/de; + baryCoord[1] = -double( x*v0[1]-x*v2[1]-v0[0]*y+v0[0]*v2[1]-v2[0]*v0[1]+v2[0]*y)/de; + baryCoord[2] = 1-baryCoord[0]-baryCoord[1]; + + ps.AddTextureSample(f, baryCoord, vcg::Point2i(x,y)); + in = true; + } else { + // Check wheter a pixel outside (on a border edge side) triangle affects color inside it + vcg::Point2 px(x, y); + vcg::Point2 closePoint; + int closeEdge = -1; + S minDst = FLT_MAX; + + for (int i=0, t=0; t<2 && i<3 && (edgeMask>>i)%2 ; ++i) + { + vcg::Point2 close; + S dst; + if ( (flipped && n[i]<0 || !flipped && n[i]>0) && + (dst = ((close = ClosestPoint(borderEdges[i], px)) - px).Norm()) < minDst && + close.X() > px.X()-1 && close.X() < px.X()+1 && + close.Y() > px.Y()-1 && close.Y() < px.Y()+1) + { + minDst = dst; + closePoint = close; + closeEdge = i; + ++t; + } + } + + if (closeEdge >= 0) + { + // Add x,y sample with closePoint barycentric coords and -min + typename MetroMesh::CoordType baryCoord; + baryCoord[closeEdge] = (closePoint - borderEdges[closeEdge].P(1)).Norm()/edgeLength[closeEdge]; + baryCoord[(closeEdge+1)%3] = 1 - baryCoord[closeEdge]; + baryCoord[(closeEdge+2)%3] = -minDst; + ps.AddTextureSample(f, baryCoord, vcg::Point2i(x,y)); + in = true; + } else if (in) break; + } + n[0] += dn0; + n[1] += dn1; + n[2] += dn2; + } + b0 += db0; + b1 += db1; + b2 += db2; + } +} + +template +static void TextureCorrected(MetroMesh & m, VertexSampler &ps, int textureWidth, int textureHeight) +{ + typedef typename MetroMesh::FaceIterator FaceIterator; + FaceIterator fi; + + printf("Similar Triangles face sampling\n"); + for(fi=m.face.begin(); fi != m.face.end(); fi++) + if (!fi->IsD()) + { + vcg::Point2f ti[3]; + for(int i=0;i<3;++i) + ti[i]=vcg::Point2f((*fi).WT(i).U() * textureWidth - 0.5, (*fi).WT(i).V() * textureHeight + 0.5); + //vcg::tri::SurfaceSampling::SingleFaceRaster(*fi, ps, ti[0],ti[1],ti[2]); + SingleFaceRasterWEdge(*fi, ps, ti[0],ti[1],ti[2]); + } +} + +#endif \ No newline at end of file