dev-wuyize
karlis 2023-03-21 14:13:41 +08:00
commit 4d16b47edd
13 changed files with 381 additions and 143 deletions

View File

@ -157,6 +157,7 @@
<ClCompile Include="src\Renderer\VirtualTextureManager.cpp" /> <ClCompile Include="src\Renderer\VirtualTextureManager.cpp" />
<ClCompile Include="src\SvgParser.cpp" /> <ClCompile Include="src\SvgParser.cpp" />
<ClCompile Include="src\Editor\EditorWidgetComponent\StrokeStyleWidget.cpp" /> <ClCompile Include="src\Editor\EditorWidgetComponent\StrokeStyleWidget.cpp" />
<QtUic Include="EditorLayerInfoWidget.ui" />
<QtUic Include="EditorSettingWidget.ui" /> <QtUic Include="EditorSettingWidget.ui" />
<QtUic Include="EditorWidget.ui" /> <QtUic Include="EditorWidget.ui" />
<QtUic Include="EditorWidgetItem.ui" /> <QtUic Include="EditorWidgetItem.ui" />

View File

@ -97,6 +97,9 @@
<QtUic Include="EditorSettingWidget.ui"> <QtUic Include="EditorSettingWidget.ui">
<Filter>Form Files</Filter> <Filter>Form Files</Filter>
</QtUic> </QtUic>
<QtUic Include="EditorLayerInfoWidget.ui">
<Filter>Form Files</Filter>
</QtUic>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="src\Editor\EditorWidgetItem.cpp"> <ClCompile Include="src\Editor\EditorWidgetItem.cpp">
@ -222,9 +225,6 @@
<ClCompile Include="src\NavigationBarWidget.cpp"> <ClCompile Include="src\NavigationBarWidget.cpp">
<Filter>Source Files\Editor</Filter> <Filter>Source Files\Editor</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\Editor\EditorWidgetComponent\ColorPicker.cpp">
<Filter>Source Files\Editor</Filter>
</ClCompile>
<ClCompile Include="src\Editor\EditorWidget.cpp"> <ClCompile Include="src\Editor\EditorWidget.cpp">
<Filter>Source Files\Editor</Filter> <Filter>Source Files\Editor</Filter>
</ClCompile> </ClCompile>
@ -261,6 +261,9 @@
<ClCompile Include="src\Editor\DataManager\ProjectDataManager.cpp"> <ClCompile Include="src\Editor\DataManager\ProjectDataManager.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="src\Editor\EditorWidgetComponent\ColorPicker.cpp">
<Filter>Source Files\Editor</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<QtMoc Include="src\Renderer\RendererGLWidget.h"> <QtMoc Include="src\Renderer\RendererGLWidget.h">

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditorLayerInfoWidget</class>
<widget class="QWidget" name="EditorLayerInfoWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="widgetLayout">
<item row="0" column="0" colspan="2">
<widget class="QtMaterialTextField" name="name">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QtMaterialTextField" name="rotation">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QtMaterialTextField" name="offsetX">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QtMaterialTextField" name="offsetY">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QtMaterialTextField" name="scaleX">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QtMaterialTextField" name="scaleY">
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QtMaterialCheckBox" name="flipX">
<property name="text">
<string>水平翻转</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QtMaterialCheckBox" name="flipY">
<property name="text">
<string>竖直翻转</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QListWidget" name="styleList"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QtMaterialTextField</class>
<extends>QLineEdit</extends>
<header location="global">qtmaterialtextfield.h</header>
</customwidget>
<customwidget>
<class>QtMaterialCheckBox</class>
<extends>QCheckBox</extends>
<header location="global">qtmaterialcheckbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1132,12 +1132,6 @@ void main()
vec2 pTemp = path[pathIndex + 1]; vec2 pTemp = path[pathIndex + 1];
if (isinf(pTemp.x)) if (isinf(pTemp.x))
{ {
if(endType == 4)
{
//onVeryEnd = false;
tangentBeginNext = tangentFirstBegin;
}
else
onVeryEnd = true; onVeryEnd = true;
} }
else else
@ -1150,8 +1144,17 @@ void main()
tangentBeginNext = normalize(pNext[0] - pNext[2]); tangentBeginNext = normalize(pNext[0] - pNext[2]);
} }
} }
else
{
if(endType == 4)
{
//onVeryEnd = false;
tangentBeginNext = tangentFirstBegin;
}
else else
onVeryEnd = true; onVeryEnd = true;
}
float d = cubic_bezier_dis(localUV, p[0], p[1], p[2], p[3], true); float d = cubic_bezier_dis(localUV, p[0], p[1], p[2], p[3], true);
if (d <= strokeWidth) if (d <= strokeWidth)
@ -1199,7 +1202,7 @@ void main()
} }
} }
tangentEndLast = tangentEnd; tangentEndLast = tangentEnd;
if(pathIndex == 0) if(pathIndex == 4)
tangentFirstBegin = tangentBegin; tangentFirstBegin = tangentBegin;
} }
p3Last = p[3]; p3Last = p[3];

View File

@ -3,6 +3,7 @@
#include <QInputDialog> #include <QInputDialog>
#include <QFileDialog> #include <QFileDialog>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QMessagebox>
ElementPoolWidget::ElementPoolWidget(QWidget* parent) ElementPoolWidget::ElementPoolWidget(QWidget* parent)
: QWidget(parent) : QWidget(parent)
@ -125,7 +126,12 @@ void ElementPoolWidget::popMenu(const QPoint& pos)
{ {
menu->addAction(QString::fromLocal8Bit("添加元素从svg导入"), this, [this]() { menu->addAction(QString::fromLocal8Bit("添加元素从svg导入"), this, [this]() {
QString filePath = QFileDialog::getOpenFileName(this, QString::fromLocal8Bit("打开文件"), "", "SVG Files (*.svg)"); QString filePath = QFileDialog::getOpenFileName(this, QString::fromLocal8Bit("打开文件"), "", "SVG Files (*.svg)");
filePath = filePath.trimmed();
QFileInfo fileInfo(filePath); QFileInfo fileInfo(filePath);
if (!fileInfo.exists() || !fileInfo.isFile()) {
QMessageBox::warning(this, tr("Error"), QString::fromLocal8Bit("请选择Svg文件"));
return;
}
QString fileName = fileInfo.fileName(); QString fileName = fileInfo.fileName();
qDebug() << fileName << " " << filePath; qDebug() << fileName << " " << filePath;
this->elementManager->createSimpleElement(fileName, filePath); this->elementManager->createSimpleElement(fileName, filePath);

View File

@ -128,6 +128,8 @@ void SimpleElement::paint(QPainter* painter, QTransform transform, const LayerSt
painter->drawImage(0, 0, img); painter->drawImage(0, 0, img);
stylesHashValue = styles.getHash(); stylesHashValue = styles.getHash();
painterPathPrev = painterPath; painterPathPrev = painterPath;
imagePrev = img;
movPrev = mov;
} }
} }

View File

@ -41,6 +41,7 @@ void PixelPath::addPath(const PixelPath& path)
QPainter painter(&pixmap); QPainter painter(&pixmap);
painter.drawPixmap(0, 0, path.getPixmap()); painter.drawPixmap(0, 0, path.getPixmap());
this->painterPath.addPath(path.getPainterPath()); this->painterPath.addPath(path.getPainterPath());
this->originPath.addPath(path.originPath);
boundingRect = boundingRect.united(path.getBoundingRect()); boundingRect = boundingRect.united(path.getBoundingRect());
} }
@ -52,6 +53,7 @@ void PixelPath::addPath(const QPainterPath& path)
painter.setPen(QPen(Qt::black,1)); painter.setPen(QPen(Qt::black,1));
painter.drawPath(path); painter.drawPath(path);
this->painterPath.addPath(path); this->painterPath.addPath(path);
this->originPath.addPath(path);
boundingRect = boundingRect.united(path.boundingRect()); boundingRect = boundingRect.united(path.boundingRect());
} }
@ -67,6 +69,7 @@ void PixelPath::clear()
pixmap.fill(Qt::transparent); pixmap.fill(Qt::transparent);
boundingRect = QRectF(0, 0, 0, 0); boundingRect = QRectF(0, 0, 0, 0);
painterPath.clear(); painterPath.clear();
originPath.clear();
} }
PixelPath PixelPath::trans(QTransform& mat)const PixelPath PixelPath::trans(QTransform& mat)const
@ -78,6 +81,7 @@ PixelPath PixelPath::trans(QTransform& mat)const
painter.setTransform(mat); painter.setTransform(mat);
painter.drawPixmap(0, 0, pixmap); painter.drawPixmap(0, 0, pixmap);
result.painterPath.addPath(this->painterPath); result.painterPath.addPath(this->painterPath);
result.originPath.addPath(this->painterPath);
result.painterPath = mat.map(result.painterPath); result.painterPath = mat.map(result.painterPath);
result.boundingRect = result.painterPath.boundingRect(); result.boundingRect = result.painterPath.boundingRect();
return result; return result;

View File

@ -10,7 +10,7 @@ class PixelPath
public: public:
QRectF boundingRect; QRectF boundingRect;
QPixmap pixmap; QPixmap pixmap;
QPainterPath painterPath; QPainterPath painterPath, originPath;
int w,h; int w,h;
public: public:
PixelPath(int w=16, int h= 16); PixelPath(int w=16, int h= 16);

View File

@ -51,7 +51,7 @@ void PreviewWindow::paintGL()
glClearColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF(), backgroundColor.alphaF()); glClearColor(backgroundColor.redF(), backgroundColor.greenF(), backgroundColor.blueF(), backgroundColor.alphaF());
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
painter->begin(this); painter->begin(this);
painter->setWindow(0, 0, logicalSize.width(), logicalSize.height()); painter->setWindow(0, 0, logicalSize.width() * devicePixelRatioF(), logicalSize.height() * devicePixelRatioF());
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
painter->setRenderHint(QPainter::HighQualityAntialiasing); painter->setRenderHint(QPainter::HighQualityAntialiasing);
layerManager->paint(painter, this->size(), currentLayer); layerManager->paint(painter, this->size(), currentLayer);

View File

@ -79,34 +79,38 @@ FolderLayerWrapper* PaintingUtil::handleLayerWrapper(LayerWrapper* nowLayer, QTr
} }
PixelPath pixelPath = nowLayer->getCache(); PixelPath pixelPath = nowLayer->getCache();
QPainterPath painterPath = pixelPath.getPainterPath(); QPainterPath painterPath = pixelPath.originPath;
QRectF bound = painterPath.boundingRect(); QRectF bound = painterPath.boundingRect();
//qDebug() << leafLayer<<"------" << painterPath; //qDebug() << leafLayer<<"------" << painterPath;
// transform to -1£¬ 1 // transform to -1£¬ 1
QTransform trans; QTransform trans;
double maxLen = std::max(bound.width(), bound.height()); double maxLen = std::max(bound.width(), bound.height()) * 1.00001;
qDebug() << maxLen << bound; qDebug() << maxLen << bound;
trans.scale(1 / maxLen, 1 / maxLen); trans.scale(1 / maxLen, 1 / maxLen);
trans.translate(-bound.center().x(), -bound.center().y()); trans.translate(-bound.center().x(), -bound.center().y());
painterPath = trans.map(painterPath); painterPath = trans.map(painterPath);
shared_ptr<vector<vector<Renderer::Point> >> contour = std::make_shared<vector<vector<Renderer::Point> >>(PainterPathUtil::transformToLines(painterPath)); shared_ptr<vector<vector<Renderer::Point> >> contour = std::make_shared<vector<vector<Renderer::Point> >>(PainterPathUtil::transformToLines(painterPath));
QSize screenSize = QSize(1024, 1024); QSize screenSize = QSize(1080, 1080);
ElementTransform elementTransform; ElementTransform elementTransform;
transform = trans.inverted() * transform * QTransform::fromScale(2. / screenSize.width(), 2. / screenSize.height()) * QTransform::fromTranslate(-1, -1) * QTransform::fromScale(1, -1); transform = trans.inverted() * transform * QTransform::fromScale(2. / screenSize.width(), 2. / screenSize.height()) * QTransform::fromTranslate(-1, -1) * QTransform::fromScale(1, -1);
auto baseStyles = leafLayer->styles.toBaseStyles(); auto baseStyles = leafLayer->styles.toBaseStyles();
Renderer::BaseElement element;
element.contour = contour;
for (auto& baseStyle : baseStyles) { for (auto& baseStyle : baseStyles) {
double lineWidth = 0; double lineWidth = 0;
std::shared_ptr<MaterialStyle> material;
if (baseStyle.material->type() == Renderer::MaterialStyleType::kStroke) { if (baseStyle.material->type() == Renderer::MaterialStyleType::kStroke) {
auto material = std::static_pointer_cast<MaterialStyleStroke>(baseStyle.material); std::shared_ptr copy = baseStyle.material->clone();
material->halfWidth /= maxLen; std::static_pointer_cast<MaterialStyleStroke>(copy)->halfWidth /= maxLen;
lineWidth = material->halfWidth; lineWidth = std::static_pointer_cast<MaterialStyleStroke>(copy)->halfWidth;
qDebug() << material->halfWidth; material = copy;
} }
else
material = baseStyle.material;
QPainterPathStroker stroker; QPainterPathStroker stroker;
stroker.setWidth(lineWidth * 2); stroker.setWidth(lineWidth * 2);
stroker.setCapStyle(Qt::RoundCap); stroker.setCapStyle(Qt::RoundCap);
@ -114,17 +118,14 @@ FolderLayerWrapper* PaintingUtil::handleLayerWrapper(LayerWrapper* nowLayer, QTr
QPainterPath strokePath = stroker.createStroke(painterPath); QPainterPath strokePath = stroker.createStroke(painterPath);
auto rect = transform.map(strokePath).boundingRect(); auto rect = transform.map(strokePath).boundingRect();
elementTransform.bound = glm::vec4(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height()); elementTransform.bound = glm::vec4(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + rect.height());
qDebug() << elementTransform.bound.x << elementTransform.bound.y << elementTransform.bound.z << elementTransform.bound.z; //qDebug() << elementTransform.bound.x << elementTransform.bound.y << elementTransform.bound.z << elementTransform.bound.z;
transform = transform.inverted(); transform = transform.inverted();
elementTransform.transform = glm::mat3x2( elementTransform.transform = glm::mat3x2(
transform.m11(), transform.m12(), transform.m21(), transform.m11(), transform.m12(), transform.m21(),
transform.m22(), transform.m31(), transform.m32() transform.m22(), transform.m31(), transform.m32()
); );
//qDebug() << transform;
elementTransform.zIndex = 0; elementTransform.zIndex = 0;
painting.addElement(BaseElement{ contour, material }, elementTransform);
element.style = baseStyle.material;
painting.addElement(element, elementTransform);
} }
return nullptr; return nullptr;

View File

@ -4,6 +4,7 @@
#include "ElementRendererTest.h" #include "ElementRendererTest.h"
#include "Renderer/Painting/ElementStyle.h" #include "Renderer/Painting/ElementStyle.h"
#include "Renderer/Painting/MaterialStyleFill.h" #include "Renderer/Painting/MaterialStyleFill.h"
#include "Editor/util/SvgFileLoader.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace Renderer; using namespace Renderer;
@ -203,6 +204,30 @@ namespace UnitTest
w.show(); w.show();
a.exec(); a.exec();
} }
TEST_METHOD(TestBothSidesClosed)
{
QPainterPath closedPath;
SvgFileLoader().loadSvgFile("../../svg/4_L0.svg", closedPath);
closedPath = QTransform::fromScale(5, 5).map(closedPath);
QApplication a(argc, argv);
class StyleStrokeRadialGradient : public Renderer::ElementStyle
{
virtual std::vector<Renderer::BaseStyle> toBaseStyles() const override
{
std::map<float, Material> materialMap = {
{0.20, Material{QColor(255,255,255)}},
{0.60, Material{QColor(165,176,207)}},
{1.00, Material{QColor(58,64,151)}}
};
return { BaseStyle(std::make_shared<TransformStyle>(),
std::make_shared<MaterialStyleStroke>(20, StrokeType::kLeftSide, StrokeEndType::kClosed,
std::make_shared<StrokeRadialGradient>(materialMap, false))) };
}
} style;
TestGLWidget w(style, closedPath);
w.show();
a.exec();
}
}; };
TEST_CLASS(ElementRendererStokeMaterialTest) TEST_CLASS(ElementRendererStokeMaterialTest)

115
test.json
View File

@ -3,7 +3,7 @@
"elements": [ "elements": [
{ {
"data": { "data": {
"include": "../svg/2.svg" "include": "/svg/2.svg"
}, },
"name": "ababa", "name": "ababa",
"type": "svg-file" "type": "svg-file"
@ -17,10 +17,17 @@
}, },
{ {
"data": { "data": {
"include": "../svg/0.svg" "include": "/svg/0.svg"
}, },
"name": "ababa2", "name": "ababa2",
"type": "svg-file" "type": "svg-file"
},
{
"data": {
"include": "/svg/4_L0.svg"
},
"name": "4_L0.svg",
"type": "svg-file"
} }
], ],
"height": 1080, "height": 1080,
@ -29,46 +36,22 @@
"children": [ "children": [
{ {
"children": [ "children": [
{
"element": 0,
"is-folder": false,
"name": "Leaf1",
"styles": [
{
"enableEachSideIndependent": false,
"left": "AAAAQAEAIZwAf///qqr//w==",
"right": "AADgQAAACJw=",
"type": "stroke"
}
],
"transform": {
"offset": {
"x": 0,
"y": 0
},
"rotation": 0,
"scale": {
"x": 1,
"y": 1
}
}
},
{ {
"element": 0, "element": 0,
"is-folder": false, "is-folder": false,
"name": "Leaf2", "name": "Leaf2",
"styles": [ "styles": [
{ {
"enableEachSideIndependent": false, "enableEachSideIndependent": true,
"left": "AAAAQAEAIZwAf////1UA/w==", "left": "AABAQAEAIZwAf///AFqe/w==",
"right": "AADgQAEACJwAf///AFqe/w==", "right": "AABAQAAACJw=",
"type": "stroke" "type": "stroke"
} }
], ],
"transform": { "transform": {
"offset": { "offset": {
"x": 150, "x": 501,
"y": 0 "y": -3
}, },
"rotation": 0, "rotation": 0,
"scale": { "scale": {
@ -83,8 +66,8 @@
"referenced-by": 1, "referenced-by": 1,
"transform": { "transform": {
"offset": { "offset": {
"x": 50, "x": 503,
"y": 50 "y": 36
}, },
"rotation": 0, "rotation": 0,
"scale": { "scale": {
@ -96,15 +79,73 @@
{ {
"element": 1, "element": 1,
"is-folder": false, "is-folder": false,
"name": "ReferencingGroupLayer", "name": "子图层-2",
"styles": [ "styles": [
], ],
"transform": { "transform": {
"offset": { "offset": {
"x": 100, "x": 1,
"y": 0 "y": 986
}, },
"rotation": 45, "rotation": 0,
"scale": {
"x": 1,
"y": 1
}
}
},
{
"element": 1,
"is-folder": false,
"name": "子图层-3",
"styles": [
],
"transform": {
"offset": {
"x": -959,
"y": -5
},
"rotation": 0,
"scale": {
"x": 1,
"y": 1
}
}
},
{
"element": 1,
"is-folder": false,
"name": "子图层-4",
"styles": [
],
"transform": {
"offset": {
"x": -958,
"y": 980
},
"rotation": 0,
"scale": {
"x": 1,
"y": 1
}
}
},
{
"element": 3,
"is-folder": false,
"name": "子图层-5",
"styles": [
{
"material": "AH8A/wBanv8=",
"type": "fill"
}
],
"transform": {
"offset": {
"x": 473,
"y": 419
},
"rotation": 0,
"scale": { "scale": {
"x": 1, "x": 1,
"y": 1 "y": 1

View File

@ -0,0 +1,60 @@
# 图层删除
当删除一个节点时,该节点一定满足且只满足以下条件中的一条:
1. 是一个Leaf节点引用了其他的Element
2. 是一个Folder节点没有GroupElement引用没有引用任何其他节点因为是Folder
3. 是一个Folder节点存在对应的GroupElement引用。
其中对于第1条由于Leaf节点被删除只影响其父亲Folder的属性所以显然在删除Leaf节点时是绝对安全的。
对于第2条对于其父亲Folder其被删除只影响其父亲Folder的属性所以不会影响其父亲对于其孩子有可能存在以下情况
1. 孩子中不存在任何被引用的情况:删除安全;
2. 孩子中存在Folder节点被GroupElement引用不安全需要手动将情况转换为第1条后才可以删除。
对于第3条必须首先手动解除其本身的引用然后将转变为第2条。
# 图元删除
**显然**无论是什么种类的图元删除一个图元会影响到的元素只有引用它的图层所以我们将SimpleElement与GroupElement合并成一种情况讨论。
当删除一个图元时,该图元一定满足且只满足以下条件中的一条:
1. 是一个GraphicElement未被其他图层引用
2. 是一个GraphicElement被其他图层引用。
对于第1条删除绝对安全。
对于第2条将引用关系手动解除后删除安全。
# 图层移动
## 条件分析
图层移动时,显然,被移动的节点只影响它本身及它的子图层,
移动的目标节点只影响它本身及它的父图层,那么:
(1) 被移动的图层一定满足且只满足以下条件中的一条:
1. 是一个Folder节点无对应的GroupElement引用子节点无引用GroupElement
2. 是一个Folder节点无对应的GroupElement引用子节点存在引用GroupElement
3. 是一个Folder节点存在对应的GroupElement引用
4. 是一个Leaf节点引用了GroupElement
5. 是一个Leaf节点未引用GroupElement。
(2) 目标FolderLayer一定满足且只满足以下条件中的一条
1. 自身无对应的GroupElement引用无父节点有GroupElement引用
2. 自身无对应的GroupElement引用存在父节点有GroupElement引用
3. 自身存在对应的GroupElement引用。
显然,以上条件的全组合可以覆盖所有情况。
由于:
- `(1) 1``(2) 中的任一条件`进行组合显然是绝对安全的;
- `(1) 3` 中自身提供的GroupElement并不会影响移动
- `(1) 5``(2) 中的任一条件`进行组合显然是绝对安全的;
- `(1) 中的任一条件``(2) 1` 进行组合显然是绝对安全的。
所以接下来对于除去以上情况的所有组合情况进行讨论。
## 讨论证明
### `(1) 2` - `(2) 2`
当且仅当被移动的图层的子节点引用的GroupElement 与 目标图层父节点提供的GroupElement 相同时,移动不安全。
### `(1) 4` - `(2) 2`
当且仅当被移动的图层引用的GroupElement 与 目标图层父节点提供的 GroupElement 相同时,移动不安全。
### `(2) 3` 的特殊讨论
当且仅当自身提供的GroupElement 被 被移动图层或其子节点引用时需要处理其自身情况否则无视其自身提供的GroupElement`(2) 1``(2) 2` 视为同情况处理。
## 结论
当且仅当被移动的图层或其子节点所引用的GroupElement 与 目标图层或其父节点所提供的GroupElement 相同时,移动不安全;其余情况均安全。