Flutter 中的生成艺术

Flutter 中的生成艺术最近 我在 FlutterCon Berlin 2023 上发表了一场关于使用低级 API 在 Flutter 中创建动画的演讲 当我准备演讲时 内容逐渐涉及艺术 更具体地说 是生成艺术及其技术历史

欢迎大家来到IT世界,在知识的湖畔探索吧!

最近,我在 FlutterCon Berlin 2023 上发表了一场关于使用低级 API 在 Flutter 中创建动画的演讲,当我准备演讲时,内容逐渐涉及艺术,更具体地说,是生成艺术及其技术历史。

Flutter 中的生成艺术



欢迎大家来到IT世界,在知识的湖畔探索吧!

我受到维拉·莫尔纳尔 (Vera Molnar) 的极大启发,她是一位旅居法国的伟大匈牙利艺术家,现年 90 多岁,仍在创作作品。 她被认为是计算机生成艺术的先驱之一。

从很小的时候起,莫尔纳就对随机性着迷,并且是使用算法渲染艺术品的早期适应者之一,在计算机生成的艺术被认为是非人性化且其真实性受到高度怀疑的时代,她认为利用随机性进行工作 机器及其高效产生大量输出的能力实际上证明了这些论点的反面。

Flutter 中的生成艺术

因为当您使用计算机时,您正在使用它作为一种工具来实现与您自己的想象力更紧密的结合。 您正在发挥您的创造力和好奇心,并通过一种媒介表达自己,这种媒介为您提供了比体力劳动所能提供的更多探索空间。

多年来,莫尔纳制作生成艺术品的方法和设备不断发展。 事实上,早在她接触电脑的十年前,她就已经开始创作“生成”艺术了。

根据定义,生成艺术是艺术家使用一个系统(例如一组自然语言规则、计算机程序或机器),然后以某种程度的自主性将其启动的任何实践。

在 50 年代,她向我们介绍了“Machine Imaginere”这个术语,即想象中的机器。 她使用了数学的基本原理以及模拟“随机发生器”,例如掷骰子。 通过这种方式,她“制作”了一系列绘图,一次对一个参数进行修改。 然后她会精心手工制作这些变体。

Flutter 中的生成艺术

在她接触到计算机后,她最初使用的是甚至没有显示器的 IBM 大型机。 当她过渡到更先进的系统时,她使用 BASIC 和 Fortran 等编程语言为机器提供控制连接的笔式绘图仪的特定指令。 然后,笔式绘图仪将使用墨水笔和机器人手臂将代码转换为绘图。

Flutter 中的生成艺术

当我了解到用代码进行绘图的历史有多悠久,以及像莫尔纳和其他许多人这样杰出的艺术家如何创作出我无法想象的艺术作品时,我真的很着迷,在他们那个时代有限的技术下是不可能实现的。

Flutter 中的生成艺术

Flutter 中的生成艺术

如今,非凡的艺术作品正在利用我们这个时代的先进技术来制作,而 Flutter 凭借其跨平台功能和开箱即用的 API 成为了此类工作的绝佳工具。

受到 Vera Molnar 的艺术风格和想法的启发,我们将通过探索 Flutter 的 CustomPainter 和 Canvas API 来了解生成艺术的一些基本原则,并且我们将引入动画来使我们的艺术作品变得栩栩如生。

让我们开始编码吧! ‍

Flutter 的 CustomPainter 和 Canvas API

通过在小部件的构建方法中添加 CustomPaint 小部件,并为其提供 CustomPainter 实现,您可以快速访问 Canvas API 以开始在屏幕上自由绘画。

@override Widget build(BuildContext context) { return CustomPaint( painter: SquareCustomPainter(), ); } class SquareCustomPainter extends CustomPainter { //... }

欢迎大家来到IT世界,在知识的湖畔探索吧!

CustomPainter 实现重写了 2 个方法,paint 方法是使用 Canvas 执行绘画的地方,shouldRepaint 方法允许您指定何时必须再次调用 Paint 方法来更新画布。 我们现在可以返回 false。

欢迎大家来到IT世界,在知识的湖畔探索吧!class SquareCustomPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { //... } @override bool shouldRepaint(covariant SquareCustomPainter oldDelegate) { return false; } }

让我们从简单的开始,在paint方法中使用canvas的drawRect API绘制一个矩形

@override void paint(Canvas canvas, Size size) { canvas.drawRect(); }

绘制矩形的方法有多种。 例如, Rect.fromCenter 允许您通过提供矩形中心的偏移量及其宽度和高度来绘制矩形。 在这里,我们使用画布的中心来使矩形居中。

欢迎大家来到IT世界,在知识的湖畔探索吧!@override void paint(Canvas canvas, Size size) { final center = Offset( size.width / 2, size.height / 2 ); canvas.drawRect( Rect.fromCenter( center: center, width: size.shortestSide * 0.8, height: size.shortestSide * 0.8, ), /* ... */, ); }

然后,您可以添加一个 Paint 对象,您可以从中指定以下属性:

style 采用 PaintingStyle.lines 或 PaintingStyle.fill 枚举值来指定疼痛的样式。

适用于填充或描边的颜色,具体取决于您提供的样式。

描边样式的描边宽度。

@override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 3; final center = Offset( size.width / 2, size.height / 2 ); canvas.drawRect( Rect.fromCenter( center: center, width: size.shortestSide * 0.8, height: size.shortestSide * 0.8, ), paint, ); }
Flutter 中的生成艺术

Flutter 文档提供了有用的图像,向您展示创建矩形的不同 API 如何工作:

Flutter 中的生成艺术

让我们使用 fromPoints API 作为另一个示例:

欢迎大家来到IT世界,在知识的湖畔探索吧!@override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 3; final side = size.shortestSide * 0.8; canvas.drawRect( Rect.fromPoints( Offset.zero, Offset(side, side), ), paint, ); }

您会注意到,如果您尝试这样做,您会看到矩形放置在左上角而不是居中的结果。 这让我想到了使用画布时的一个重要概念。

使用 Canvas API 居中

当您使用 CustomPainter 或任何其他方式(如自定义 RenderObjects)渲染画布时,它将位于包含它的小部件的左上角,并且您所做的任何偏移或平移都是相对于 0 by 0 点完成的 那是左上角。

Flutter 中的生成艺术

因此,如果你想绘制居中的东西,你需要:

首先使用 canvas.save() 保存画布的当前状态

将画布平移到其中心偏移。

做图吧

并使用 canvas.restore() 重置画布

canvas.save(); // 1 canvas.translate(center.dx, center.dy); // 2 canvas.drawRect( // 3 Rect.fromPoints( Offset(-side / 2, -side / 2), Offset(side / 2, side / 2), ), paint, ); canvas.restore(); // 4
Flutter 中的生成艺术

生成艺术工具

在创建生成艺术作品时涉及到一些工具,我想在我们使用 Canvas API 实现这些工具时简要提及一些工具。

平铺 — 生成艺术工具 #1

就像平铺墙壁一样,您可以通过水平和垂直重复来平铺艺术品,因此我们可以通过在画布上创建网格来将平铺引入到之前创建的正方形中。 我们通过计算包含 CustomPainter 的小部件中可以水平和垂直放置的正方形数量来实现这一点,并使用简单的 for 循环绘制正方形。 您可以通过 CustomPainter 的 Paint 方法访问该小部件的大小。

我们还将计算偏移绘图的量以使最终网格居中

欢迎大家来到IT世界,在知识的湖畔探索吧!@override void paint(Canvas canvas, Size size) { final xCount = ((size.width + gap) / (side + gap)).floor(); final yCount = ((size.height + gap) / (side + gap)).floor(); final contentSize = Size( (xCount * side) + ((xCount - 1) * gap), (yCount * side) + ((yCount - 1) * gap), ); final offset = Offset( (size.width - contentSize.width) / 2, (size.height - contentSize.height) / 2, ); canvas.save(); canvas.translate(offset.dx, offset.dy); for (int index = 0; index < totalCount; index++) { int i = index ~/ yCount; int j = index % yCount; canvas.drawRect( Rect.fromLTWH( (i * (side + gap)), (j * (side + gap)), side, side, ), paint, ); } canvas.restore(); }

结果:

Flutter 中的生成艺术

递归——生成艺术工具#2

使用递归的编程能力来创建可以重复嵌套出现的图稿元素,直到满足一个或多个条件为止,为您在画布中操纵线条和形状提供了无限的可能性。 对于我们的正方形网格,我们将以受 Vera Molnar 的一件艺术品启发的方式实现递归。

我们可以通过在递归函数中使用 canvas.drawRect() 方法来做到这一点,该函数只是不断绘制边长在每次递归调用时减小的矩形,直到达到指定的最小长度。

// Paint method for (int index = 0; index < totalCount; index++) { int i = index ~/ yCount; int j = index % yCount; drawNestedSquares( // Recursively draws squares canvas, Offset( (i * (sideLength + gap)), (j * (sideLength + gap)), ), sideLength, paint, ); }

递归函数:

欢迎大家来到IT世界,在知识的湖畔探索吧!void drawNestedSquares( Canvas canvas, Offset start, double side, Paint paint, ) { if (sideLength < minSideLength) return; canvas.drawRect( Rect.fromLTWH( start.dx, start.dy, side, side, ), paint, ); final nextSideLength = side * 0.8; final nextStart = Offset( start.dx + side / 2 - nextSideLength / 2, start.dy + side / 2 - nextSideLength / 2, ); drawNestedSquares(canvas, nextStart, nextSideLength, paint); }

到目前为止的结果:

Flutter 中的生成艺术

随机性 — 生成艺术工具 #3

如果这件作品不包含最令她着迷的东西——随机性,那么它就不是受维拉·莫尔纳启发的作品,随机性实际上是一个强大的工具,可以带来真正令人着迷的结果。

我们可以通过几种方式在这里引入随机性:

随机化递归函数中下一个正方形的边长。

引入随机深度值,最终使最小正方形的大小随机化。

void drawNestedSquares( Canvas canvas, Offset start, double side, Paint paint, int depth, // ⬅️ ) { if (side < minSideLength || depth <= 0) return; canvas.drawRect( Rect.fromLTWH( start.dx, start.dy, side, side, ), paint, ); final nextSideLength = side * (random.nextDouble() * 0.5 + 0.5); // ⬅️ final nextStart = Offset( start.dx + side / 2 - nextSideLength / 2, start.dy + side / 2 - nextSideLength / 2, ); drawNestedSquares(canvas, nextStart, nextSideLength, paint, depth - 1); }

结果:

Flutter 中的生成艺术

小部件手册和输入参数实验

与生成艺术家从一开始就一直在做的事情类似,您可以将各种参数设置为系统输入,并继续尝试非随机值,直到找到与您想象的一致的东西,或者更好的是,您可能会意外地偶然发现一些效果 这比你想象的还要疯狂!

为了能够在 Flutter 中快速轻松地完成此操作,我使用了 Widgetbook 包并设置了一个单独的 main.widgetbook.dart 文件,以便我可以运行单独的 Flutter 应用程序。 在此 Widgetbook 应用程序中,除了能够测试我构建的艺术品的不同输入参数之外,我还可以创建所有这些艺术品小部件的目录,并且可以在构建它们时轻松访问它们。

按照文档操作,在您的项目中设置 Widgetbook 应该很容易。 与本文相关的是旋钮功能。 这就是我将在整篇文章中使用的内容来说明一些概念并尝试系统输入。

例如,考虑我们的随机递归正方形,如果网格间隙为 0,它会是什么样子? 如果正方形更厚、更小或更大怎么办? 我们可以尝试所有这些,并通过使用带有小部件的旋钮立即查看结果:

欢迎大家来到IT世界,在知识的湖畔探索吧!WidgetbookUseCase( name: 'Randomized', builder: (context) { return RandomizedRecursiveSquaresGrid( gap: context.knobs.double.slider( // ⬅️ label: 'Gap', initialValue: 5, min: 0, max: 50, ), // ... ); }, )

这是它的外观的预览:

Flutter 中的生成艺术

好吧,黑白有点无聊,让我们混合一些颜色吧!

颜色

当维拉·莫尔纳 (Vera Molnar) 第一次开始创作计算机生成艺术时,她使用的是没有颜色的笔式绘图仪(下图中该设备的旧版本)。 所以她会画出图画,然后手工上色。 她尝试了许多材料来实现颜色,在某些时候她甚至使用了血液!

Flutter 中的生成艺术

值得庆幸的是,我们在 Flutter 中有 Color 类,但我们不需要这样做。

随机颜色很棘手!

但当你想要随机化颜色时,它实际上会变得有点棘手。 简单直接的解决方案是有一个可供您随机选择的颜色列表。 但这还不够有趣,还有更好的方法!

人们可以想到的第一种替代方案可能是随机化颜色 RGB 格式中的 R、G 和/或 B 值。 然而,正如您从下面的 GIF 中看到的那样,这会产生不太漂亮或不丰富的颜色。

Color.fromARGB(a, r, g, b)
Flutter 中的生成艺术

最好的替代方法是使用 HSL 值(色调、饱和度和亮度)。 您可以简单地将饱和度和亮度设置为您喜欢的固定值,然后随机化色调值,这样您就可以通过一系列可行的颜色进行随机化,您可以使用固定值修改其丰富度和亮度。

欢迎大家来到IT世界,在知识的湖畔探索吧!HSLColor.fromAHSL(a, h /* randomized */, s, l).toColor()
Flutter 中的生成艺术

现在,如果我们在之前的作品中引入随机颜色,我们可以获得以下结果:

Flutter 中的生成艺术

Flutter 中的生成艺术

置换——生成艺术工具#4

当谈到生成艺术时,混乱是一件好事! 我们可以利用另一种有用的工具——位移——来给我们的艺术作品带来一些混乱。

例如,回到我们原来的正方形,我可以说我想将每个角从原来的位置移动随机距离以创建一个随机多边形。 这种方式将位移与随机性结合起来,产生更深层次的混乱。

Flutter 中的生成艺术

首先,我们可以使用drawPoints来绘制原始矩形点。 (drawRect只能绘制统一的矩形)

// Paint Method canvas.drawPoints( PointMode.polygon, [ topLeft, // Offset topRight, // Offset bottomRight, // Offset bottomLeft, // Offset topLeft, // Offset ], paint, );

其中 topLeft 、 topRight 等是基于正方形边长和画布居中偏移量预先计算的偏移量。 让我们将这些点偏移一定距离,该距离随机介于预定义 maxCornersOffset 变量的负值和正值之间。

欢迎大家来到IT世界,在知识的湖畔探索吧!// Paint Method topLeft += Offset( maxCornersOffset * 2 * (random.nextDouble() - 0.5), maxCornersOffset * 2 * (random.nextDouble() - 0.5), ); topRight += Offset( maxCornersOffset * 2 * (random.nextDouble() - 0.5), maxCornersOffset * 2 * (random.nextDouble() - 0.5), ); /* ... */ canvas.drawPoints( PointMode.polygon, [ topLeft, // Offset topRight, // Offset bottomRight, // Offset bottomLeft, // Offset topLeft, // Offset ], paint, );

现在我们可以返回到 Widgetbook 并尝试 maxCornerOffset 输入,看看多边形如何随着该值的增加而变得更加扭曲,反之亦然。

Flutter 中的生成艺术

重复——生成艺术工具#5

作为生成艺术工具带中的最后一个工具,我们可以简单地通过使用带有最小值和最大值的 for 循环来实现一些重复,我们可以将这些最小值和最大值作为系统的输入参数提供。

// Paint Method final repetition = random.nextInt(10) + minRepetition; for (int i = 0; i < repetition; i++) { topLeft += Offset( maxCornersOffset * 2 * (random.nextDouble() - 0.5), maxCornersOffset * 2 * (random.nextDouble() - 0.5), ); topRight += Offset( maxCornersOffset * 2 * (random.nextDouble() - 0.5), maxCornersOffset * 2 * (random.nextDouble() - 0.5), ); /* ... */ canvas.drawPoints( PointMode.polygon, [ topLeft, // Offset topRight, // Offset bottomRight, // Offset bottomLeft, // Offset topLeft, // Offset ], paint, ); }

使用我们的 Widgetbook,我们可以看到 minRepetition 值以及其他输入对随机多边形外观的影响。

Flutter 中的生成艺术

让我们添加一些收尾工作,使其成为可行的艺术品:

切换到黑暗模式,因为为什么不呢!

Flutter 中的生成艺术

2. 添加平铺(我们的第一个生成艺术工具):

Flutter 中的生成艺术

3.最后,添加颜色:

Flutter 中的生成艺术

瞧! 正如你所看到的,使用一些生成艺术基础知识和几行实现 Flutter Canvas API 基本功能的代码,我们从一个简单无聊的正方形,一直到充满形状、颜色和混乱的画布 !

但为什么停在这里呢? 让我们添加一些动画来将这种混乱带入生活!

动画片

在我们开始动画之前,最好花一些时间进行重构。 我的主要目标是可重用性和实用性 getter 和方法。 因此,我将创建一个 Polygon 类,该类将存储每个多边形的数据,例如其角偏移、颜色、重叠多边形的单次重复中的级别,以及多边形网格中的标准化位置。

欢迎大家来到IT世界,在知识的湖畔探索吧!class Polygon { final Offset topLeft; final Offset topRight; final Offset bottomRight; final Offset bottomLeft; final Color color; // Level in a set of overlapping polygons final double level; // Normalized location on x and y axis final Location location; List<Offset> get points => [ topLeft, topRight, bottomRight, bottomLeft, topLeft, ]; }

这样我就可以实现一个实用方法(例如generatePolygonSets)来填充该数据,并使我可以在我的CustomPainter的paint方法中直接使用Polygon getters。

class PolygonsCustomPainter extends CustomPainter { PolygonsCustomPainter({ required this.random, this.strokeWidth = 2, this.maxSideLength = 200, this.maxCornersOffset = 20, }) { polygons = generateDistortedPolygonsSet( // Generates the polygons random, maxSideLength: maxSideLength, maxCornersOffset: maxCornersOffset, ); } late final List<Polygon> polygons; /* ... */ @override void paint(Canvas canvas, Size size) { for (int i = 0; i < polygons.length; i++) { paint.color = polygons[i] .color .withOpacity(polygons[i].level); canvas.drawPoints( PointMode.polygon, polygons[i].points, // Polygon getter paint, ); } } }

此代码将循环遍历预先生成的多边形,并使用 Canvas 的 drawPoints API 以及点获取器和根据多边形级别改变不透明度的颜色来绘制它们,从而产生与之前相同的输出。

此外,我可以存储多边形的原始角偏移值(位移之前的方形点),使我能够实现利用 Flutter 的 Offset.lerp() 功能的 getLerpedPoints 方法。 这基本上允许我们在给定 0 到 1 之间的标准化值的情况下获得 2 个偏移之间的值。

欢迎大家来到IT世界,在知识的湖畔探索吧!class Polygon { // Displaced offsets final Offset topLeft; final Offset topRight; final Offset bottomRight; final Offset bottomLeft; // Original offsets final Offset topLeftOrigin; final Offset topRightOrigin; final Offset bottomRightOrigin; final Offset bottomLeftOrigin; /* ... */ List<Offset> getLerpedPoints(double value) { return [ Offset.lerp(topLeft, topLeftOrigin, value)!, Offset.lerp(topRight, topRightOrigin, value)!, Offset.lerp(bottomRight, bottomRightOrigin, value)!, Offset.lerp(bottomLeft, bottomLeftOrigin, value)!, Offset.lerp(topLeft, topLeftOrigin, value)!, ]; } }

现在我可以在paint方法中通过drawPoints调用使用这个getLerpedPoints getter,利用存储在Polygon类中的位置数据,并用正弦函数添加一点扭曲,因为,数学!

// Paint Method for (int i = 0; i < polygons.length; i++) { paint.color = polygons[i] .color .withOpacity(polygons[i].level); canvas.drawPoints( PointMode.polygon, polygons[i].getLerpedPoints( // Some cool math ⬇️ 0.5 * sin(polygons[i].location.x * 2 * pi) + 0.5 ), paint, ); }

这将产生以下结果:

Flutter 中的生成艺术

好吧,现在让我们真正实现动画。

思维过程

为了决定我想要制作什么动画,我从一组多边形开始,发现我可以对每个多边形的不透明度进行动画处理,使它们一个接一个地淡出,而且我还可以将每个多边形从统一的正方形动画化为其当前形状 ,也是一前一后。 重构后这现在很容易实现!

Flutter 中的生成艺术

使用 CustomPainter 制作动画

要设置动画,我们可以为 CustomPainter 提供 animationController 并将其传递给其超级构造函数的 repaint 参数。

欢迎大家来到IT世界,在知识的湖畔探索吧!class PolygonsCustomPainter extends CustomPainter { PolygonsCustomPainter({ required AnimationController animationController, // ⬅️ /* ... */ }) : super(repaint: animationController) { // ⬅️ polygons = generateDistortedPolygonsSet(/* ... */); } late final List<Polygon> polygons; /* ... */ }

这基本上会在动画控制器运行时触发重绘。 相当于实现 CustomPainter 的 shouldRepaint 方法,如下所示:

class PolygonsCustomPainter extends CustomPainter { PolygonsCustomPainter({ required AnimationController animationController, /* ... */ }) : super(repaint: animationController) { polygons = generateDistortedPolygonsSet(/* ... */); } /* ... */ @override bool shouldRepaint(covariant PolygonsCustomPainter oldDelegate) { return animationController.value != oldDelegate.animationController.value; } }

现在,由于我们想要做的不仅仅是对从状态 A 到状态 B 的所有内容进行动画处理,而且我们希望拥有连续的动画,因此我们需要实现某种延迟,为此我们可以创建多个动画类型对象

欢迎大家来到IT世界,在知识的湖畔探索吧!class PolygonsCustomPainter extends CustomPainter { PolygonsCustomPainter({ required AnimationController animationController, /* ... */ }) : super(repaint: animationController) { polygons = generateDistortedPolygonsSet(/* ... */); polygonAnimations = generatePolygonAnimations(polygons, animationController); } late final List<Animation<double>> polygonAnimations; late final List<Polygon> polygons; /* ... */ }

其中每个都是一个挂接到父动画控制器上的 Tween,具有自定义曲线和带有开始值和结束值的 Interval,我们可以通过利用 Polygon 类来指定这些值,并为我们希望每个多边形动画开始时的标准化值创建 getter, 这可以使用多边形的级别,即它在多边形的单个重复中的位置

List<Animation<double>> generatePolygonAnimations( List<Polygon> polygons, AnimationController animationController, ) { return List.generate( polygons.length, (i) { return Tween<double>(begin: 0.2, end: 1).animate( CurvedAnimation( parent: animationController, curve: Interval( polygons[i].animationStart, polygons[i].animationEnd, curve: Curves.easeInOut, ), ), ); }, ); } class Polygon { /* ... */ final double level; final Location location; double get animationStart => level; // ⬅️ double get animationEnd => (animationStart + 0.2) >= 1.0 ? 1.0 : (animationStart + 0.2); // ⬅️ }

然后我们可以轻松地在 Paint 方法中使用循环中的动画对象来对不透明度和 lerped 偏移点进行动画处理

欢迎大家来到IT世界,在知识的湖畔探索吧!// Paint method for (int i = 0; i < polygons.length; i++) { final opacity = polygons[i].level * polygonAnimations[i].value * 0.7; // ⬅️ paint.color = polygons[i].color.withOpacity(opacity); canvas.drawPoints( PointMode.polygon, polygons[I].getLerpedPoints(1 - polygonAnimations[I].value), // ⬅️ paint, ); }

产生这个输出:

Flutter 中的生成艺术

我们可以更进一步,利用对每个多边形的位置数据的访问并调整animationStart值,使其取决于多边形集的x(或y)位置。

class Polygon { /* ... */ final double level; final Location location; double get animationStart => location.x * level; // ⬅️ double get animationEnd => (animationStart + 0.2) >= 1.0 ? 1.0 : (animationStart + 0.2); }

这将导致:

Flutter 中的生成艺术

Flutter 中的生成艺术

结论

这只是介绍如何解锁 Flutter 工具并将其与一些基本的生成艺术概念相结合以开始创作精美的艺术作品。

虽然 Vera Molnar 那个时代的生成艺术家正在利用他们的编码技能,使用笔式绘图仪将艺术从数字世界转化为物理世界,但我个人认为,通过迷人和有趣的方式将这种艺术转化为普通移动应用程序用户的手中将会很棒。 令人难忘的用户界面。 另外,我们都同意我们的生活值得更多美丽!

Flutter 中的生成艺术

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/108910.html

(0)
上一篇 11分钟前
下一篇 1分钟前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信