最近在写一款软件,其中一个功能就是透视,在网上搜索了很久,翻阅了很多资料。第一份资料呈现给我的就是【猴健居士】写的一篇关于【C# 图片自由变换 任意扭曲】,我很想去实现那种效果,只是才疏学浅,无法明白其思路,即使有贴出来了代码,我也没有做到,很惭愧。
而网上其他的方式多半是与【OpenCV】结合,这种很简单,却让我头大,这对于我来说无意是接触一种新的语言,奈何时间不会那么充裕。
最终我还是决定,以C#自身的情况,来实现这种效果,我自己是无法办到的,就想到了第一次看到的那个文章,的确是用C#语言写的。只是我无法还原,于是找到了这个朋友,希望他可以帮忙。
那朋友知道的来意后,帮了我,点出了我的问题,并告诉了这段代码的存在什么问题。我在得知后,很高兴,也很感谢,先能实现再说,具体有什么问题,在实现后,应该会逐步克服。
现在贴出我在得到代码后的变动。
1、之前代码会有黑色背景,我改动过避免了这种情况,毕竟有些图片带着透明效果的
2、在循环中增加了continue,可以缩短图片的转换时间,对于有透明效果的图片很明显。
3、代码了用了重载和函数,增加了使用型
4、去掉了一部分代码,我不知道那些代码会达到什么效果,只是在代码中意义不大,暂时放弃了。
图片效果如下
我用一个类Perspective 来实现完成透视的效果,代码如下:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;namespace ImageFile
{
/// <summary>
/// 透视
/// </summary>
public class Perspective
{
private int roundup(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a > a) return (int)a; else return (int)a + 1; }
private int rounddown(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a < a) return (int)a; else return (int)a - 1; }
private int round(double a) { return (int)(a + 0.5); }
private byte byterange(double a) { int b = round(a); if (b <= 0) return 0; else if (b >= 255) return 255; else return (byte)b; }
private double AafAbs(double a) { return (((a) < 0) ? (-(a)) : (a)); }
private double aaf_min(double a, double b) { if (a < b) return a; else return b; }
private double aaf_max(double a, double b) { if (a > b) return a; else return b; }private AafPnt[] pixelgrid;
private AafPnt[] polyoverlap;
private AafPnt[] polysorted;
private AafPnt[] corners;private int outstartx;
private int outstarty;
private int outwidth;
private int outheight;private Bitmap prepImg;
private List<double> xDouList;
private List<double> yDouList;int polyoverlapsize;
int polysortedsize;int[] ja = new int[] { 1, 2, 3, 0 };
public Perspective() {
}public Perspective(double[] xcorner, double[] ycorner)
{
xDouList = xcorner.ToList();
yDouList = ycorner.ToList();
}
public Perspective(Bitmap src,double[] xcorner, double[] ycorner)
{
prepImg = src;
xDouList = xcorner.ToList();
yDouList = ycorner.ToList();
}
public Bitmap CreateTransform()
{
return CreateTransform(prepImg);
}public Bitmap CreateTransform(Bitmap src)
{
return CreateTransform(src, xDouList, yDouList);
}public Bitmap CreateTransform(Bitmap src, List<double> xcorner, List<double> ycorner)
{
int right = 0, bottom = 0;//主要是根据新图片的坐标,计算出图片的宽和高,构造目标图片的Bitmap的
double offx = xcorner[0];
double offy = ycorner[0];
for (int i = 1; i < 4; i++)
{
if (xcorner[i] < offx) offx = xcorner[i];
if (ycorner[i] < offy) offy = ycorner[i];
}for (int i = 0; i < 4; i++)
{
xcorner[i] -= offx;
ycorner[i] -= offy;
if (roundup(xcorner[i]) > right) right = roundup(xcorner[i]);
if (roundup(ycorner[i]) > bottom) bottom = roundup(ycorner[i]);
}
Bitmap dst = new Bitmap(right, bottom);
Transform(src, dst, xcorner, ycorner);
return dst;
}private void Transform(Bitmap src, Bitmap dst, List<double> xcorner, List<double> ycorner)
{
//Make sure the coordinates are valid
if (xcorner.Count != 4 || ycorner.Count != 4) {
return;
}
//Load the src bitmaps information
//create the intial arrays
//根据原图片生成一个比原图宽高多一个单位的图片,
//这个矩阵就是用来记录转换后各个像素点左上角的坐标
pixelgrid = new AafPnt[(src.Width + 1) * (src.Height + 1)];
polyoverlap = new AafPnt[16];
polysorted = new AafPnt[16];
corners = new AafPnt[4];//Load the corners array
double[] dx = { 0.0, 1.0, 1.0, 0.0 };
double[] dy = { 0.0, 0.0, 1.0, 1.0 };
for (int i = 0; i < 4; i++)
{
corners[i].x = dx[i];
corners[i].y = dy[i];
}//Find the rectangle of dst to draw to
outstartx = rounddown(xcorner[0]);
outstarty = rounddown(ycorner[0]);
outwidth = 0;
outheight = 0;
//这里计算出变换后起始点的坐标
for (int i = 1; i < 4; i++)
{
if (rounddown(xcorner[i]) < outstartx) outstartx = rounddown(xcorner[i]);
if (rounddown(ycorner[i]) < outstarty) outstarty = rounddown(ycorner[i]);
}
for (int i = 0; i < 4; i++)
{
if (roundup(xcorner[i] - outstartx) > outwidth) outwidth = roundup(xcorner[i] - outstartx);
if (roundup(ycorner[i] - outstarty) > outheight) outheight = roundup(ycorner[i] - outstarty);
}//fill out pixelgrid array
//计算出理想目标图片中各个“像素格”中的左上角的坐标
if (CreateGrid(src, xcorner, ycorner))
{
//Do the transformation
//进行转换
DoTransform(src, dst);
}
}private bool CreateGrid(Bitmap src, List<double> xcorner, List<double> ycorner)
{
//mmm geometry
double[] sideradius = new double[4];
double[] sidecos = new double[4];
double[] sidesin = new double[4];//First we find the radius, cos, and sin of each side of the polygon created by xcorner and ycorner
int j;
for (int i = 0; i < 4; i++)
{
j = ja[i];
sideradius[i] = Math.Sqrt((xcorner[i] - xcorner[j]) * (xcorner[i] - xcorner[j]) + (ycorner[i] - ycorner[j]) * (ycorner[i] - ycorner[j]));
sidecos[i] = (xcorner[j] - xcorner[i]) / sideradius[i];
sidesin[i] = (ycorner[j] - ycorner[i]) / sideradius[i];
}//Next we create two lines in Ax + By = C form
for (int x = 0; x < src.Width + 1; x++)
{
double topdist = ((double)x / (src.Width)) * sideradius[0];//每个像素点变换后的坐标点
double ptxtop = xcorner[0] + topdist * sidecos[0];
double ptytop = ycorner[0] + topdist * sidesin[0];double botdist = (1.0 - (double)x / (src.Width)) * sideradius[2];
double ptxbot = xcorner[2] + botdist * sidecos[2];
double ptybot = ycorner[2] + botdist * sidesin[2];double Ah = ptybot - ptytop;
double Bh = ptxtop - ptxbot;
double Ch = Ah * ptxtop + Bh * ptytop;//叉乘for (int y = 0; y < src.Height + 1; y++)
{
double leftdist = (1.0 - (double)y / (src.Height)) * sideradius[3];
double ptxleft = xcorner[3] + leftdist * sidecos[3];
double ptyleft = ycorner[3] + leftdist * sidesin[3];double rightdist = ((double)y / (src.Height)) * sideradius[1];
double ptxright = xcorner[1] + rightdist * sidecos[1];
double ptyright = ycorner[1] + rightdist * sidesin[1];double Av = ptyright - ptyleft;
double Bv = ptxleft - ptxright;
double Cv = Av * ptxleft + Bv * ptyleft;//Find where the lines intersect and store that point in the pixelgrid array
double det = Ah * Bv - Av * Bh;
if (AafAbs(det) < 1e-9)
{
return false;
}
else
{
int ind = x + y * (src.Width + 1);
pixelgrid[ind].x = (Bv * Ch - Bh * Cv) / det;
pixelgrid[ind].y = (Ah * Cv - Av * Ch) / det;
}
}
}
//Yayy we didn‘t fail
return true;
}private void DoTransform(Bitmap src, Bitmap dst)
{//Get source bitmap‘s information
if (src == null) return;//Create the source dib array and the dstdib array
aaf_dblrgbquad[] dbldstdib = new aaf_dblrgbquad[outwidth * outheight];
for (int i = 0; i < dbldstdib.Length; i++)
dbldstdib[i] = new aaf_dblrgbquad();//Create polygon arrays
AafPnt[] p = new AafPnt[4];
AafPnt[] poffset = new AafPnt[4];
//Loop through the source‘s pixels
//遍历原图(实质上是pixelgrid)各个点
for (int x = 0; x < src.Width; x++)
{
for (int y = 0; y < src.Height; y++)
{
//取当前点 下一点 右一点 斜右下角点 四点才组成一个四边形
//这个四边形是原图像的一个像素点
//Construct the source pixel‘s rotated polygon from pixelgrid
p[0] = pixelgrid[x + y * (src.Width + 1)];
p[1] = pixelgrid[(x + 1) + y * (src.Width + 1)];
p[2] = pixelgrid[(x + 1) + (y + 1) * (src.Width + 1)];
p[3] = pixelgrid[x + (y + 1) * (src.Width + 1)];//Find the scan area on the destination‘s pixels
int mindx = int.MaxValue;
int mindy = int.MaxValue;
int maxdx = int.MinValue;
int maxdy = int.MinValue;
for (int i = 0; i < 4; i++)
{
if (rounddown(p[i].x) < mindx) mindx = rounddown(p[i].x);
if (roundup(p[i].x) > maxdx) maxdx = roundup(p[i].x);
if (rounddown(p[i].y) < mindy) mindy = rounddown(p[i].y);
if (roundup(p[i].y) > maxdy) maxdy = roundup(p[i].y);
}int SrcIndex = x + y * src.Width;
//遍历四边形包含了目标图几个像素点
//按照相交面积占整个像素的的百分比,把颜色按照该比例存放一个目标像素点颜色的数组中
//这里计算出来的颜色只是初步颜色,还没到最终结果
//loop through the scan area to find where source(x, y) overlaps with the destination pixels
for (int xx = mindx - 1; xx <= maxdx; xx++)
{
if (xx < 0 || xx >= dst.Width)
continue;
for (int yy = mindy - 1; yy <= maxdy; yy++)
{
if (yy < 0 || yy >= dst.Height)
continue;//offset p and by (xx,yy) and put that into poffset
for (int i = 0; i < 4; i++)
{
poffset[i].x = p[i].x - xx;
poffset[i].y = p[i].y - yy;
}//FIND THE OVERLAP *a whole lot of code pays off here*
//这里则是计算出覆盖了面积占当前像素的百分比
double dbloverlap = PixOverlap(poffset);
//按照百分比来为目标像素点累加颜色
//因为一个目标像素点有可能有几个原来像素的覆盖了
if (dbloverlap > 0)
{
int dstindex = xx + yy * outwidth;
int srcWidth = src.Width;
Color srcColor = new Color();
if (SrcIndex == 0)
{
srcColor = src.GetPixel(0, 0);
}
else
{
srcColor = src.GetPixel(SrcIndex % src.Width, SrcIndex / src.Width);
}
if (srcColor.Name != "0")
{
//Add the rgb and alpha values in proportion to the overlap area
dbldstdib[dstindex].Red += (double)((srcColor.R) * dbloverlap);
dbldstdib[dstindex].Blue += (double)(srcColor.B) * dbloverlap;
dbldstdib[dstindex].Green += (double)(srcColor.G) * dbloverlap;
dbldstdib[dstindex].Alpha += dbloverlap;
}
else
{
continue;
}
}
else {
continue;
}
}
}
}
}
//Free memory no longer needed
//Create final destination bits
Color c = new Color();
//这里是实际上真正像素点的颜色,并且填到了目标图片中去
//Write to dstdib with the information stored in dbldstdib
for (int x = 0; x < outwidth; x++)
{
if (x + outstartx >= dst.Width)
continue;
for (int y = 0; y < outheight; y++)
{
if (y + outstarty >= dst.Height) {
continue;
}
int offindex = x + y * outwidth;
int dstindex = x + outstartx + (y + outstarty) * dst.Width;int dstIndexX = dstindex / dst.Width;
int dstIndexY = dstindex % dst.Width;
if (dbldstdib[offindex].Alpha==0)
{
continue;
}
else{
if (dbldstdib[offindex].Alpha > 1)
{
//handles wrap around for non-convex transformations
c = Color.FromArgb(byterange(dbldstdib[offindex].Red / dbldstdib[offindex].Alpha), byterange(dbldstdib[offindex].Green / dbldstdib[offindex].Alpha), byterange(dbldstdib[offindex].Blue / dbldstdib[offindex].Alpha));
}
else
{
//Color dstColor = dst.GetPixel(dstIndexX, dstIndexY);
c = Color.FromArgb(byterange(dbldstdib[offindex].Red + (1 - dbldstdib[offindex].Alpha) * c.R), byterange(dbldstdib[offindex].Green + (1 - dbldstdib[offindex].Alpha) * c.G), byterange(dbldstdib[offindex].Blue + (1 - dbldstdib[offindex].Alpha) * c.B));
}
}
dst.SetPixel(dstIndexY, dstIndexX, c);
}
}
}double PixOverlap(AafPnt[] p)
{
polyoverlapsize = 0;
polysortedsize = 0;double minx, maxx, miny, maxy;
int j;double z;
for (int i = 0; i < 4; i++)
{
//Search for source points within the destination quadrolateral
if (p[i].x >= 0 && p[i].x <= 1 && p[i].y >= 0 && p[i].y <= 1)
polyoverlap[polyoverlapsize++] = p[i];//Search for destination points within the source quadrolateral
if (PtinConvexPolygon(p, corners[i]))
polyoverlap[polyoverlapsize++] = corners[i];//Search for line intersections
j = ja[i];
minx = aaf_min(p[i].x, p[j].x);
miny = aaf_min(p[i].y, p[j].y);
maxx = aaf_max(p[i].x, p[j].x);
maxy = aaf_max(p[i].y, p[j].y);if (minx < 0.0 && 0.0 < maxx)
{//Cross left
z = p[i].y - p[i].x * (p[i].y - p[j].y) / (p[i].x - p[j].x);
if (z >= 0.0 && z <= 1.0)
{
polyoverlap[polyoverlapsize].x = 0.0;
polyoverlap[polyoverlapsize++].y = z;
}
}
if (minx < 1.0 && 1.0 < maxx)
{//Cross right
z = p[i].y + (1 - p[i].x) * (p[i].y - p[j].y) / (p[i].x - p[j].x);
if (z >= 0.0 && z <= 1.0)
{
polyoverlap[polyoverlapsize].x = 1.0;
polyoverlap[polyoverlapsize++].y = z;
}
}
if (miny < 0.0 && 0.0 < maxy)
{//Cross bottom
z = p[i].x - p[i].y * (p[i].x - p[j].x) / (p[i].y - p[j].y);
if (z >= 0.0 && z <= 1.0)
{
polyoverlap[polyoverlapsize].x = z;
polyoverlap[polyoverlapsize++].y = 0.0;
}
}
if (miny < 1.0 && 1.0 < maxy)
{//Cross top
z = p[i].x + (1 - p[i].y) * (p[i].x - p[j].x) / (p[i].y - p[j].y);
if (z >= 0.0 && z <= 1.0)
{
polyoverlap[polyoverlapsize].x = z;
polyoverlap[polyoverlapsize++].y = 1.0;
}
}
}//Sort the points and return the area
SortPoints();
return Area();
}private double Area()
{
double ret = 0.0;
//Loop through each triangle with respect to (0, 0) and add the cross multiplication
for (int i = 0; i + 1 < polysortedsize; i++)
ret += polysorted[i].x * polysorted[i + 1].y - polysorted[i + 1].x * polysorted[i].y;
//Take the absolute value over 2
return AafAbs(ret) / 2.0;
}private void SortPoints()
{
//Why even bother?
if (polyoverlapsize < 3)
return;//polyoverlap is a triangle, points cannot be out of order
if (polyoverlapsize == 3)
{
polysortedsize = polyoverlapsize - 1;
polysorted[0].x = polyoverlap[1].x - polyoverlap[0].x;
polysorted[0].y = polyoverlap[1].y - polyoverlap[0].y;
polysorted[1].x = polyoverlap[2].x - polyoverlap[0].x;
polysorted[1].y = polyoverlap[2].y - polyoverlap[0].y;
return;
}aaf_indll root = new aaf_indll();
root.next = null;//begin sorting the points. Note that the first element is left out and all other elements are offset by it‘s values
for (int i = 1; i < polyoverlapsize; i++)
{
polyoverlap[i].x = polyoverlap[i].x - polyoverlap[0].x;
polyoverlap[i].y = polyoverlap[i].y - polyoverlap[0].y;aaf_indll node = root;
//Loop until the point polyoverlap[i] is can be sorted (counter) clockwiswe (I‘m not sure which way it‘s sorted)
while (true)
{
if (node.next != null)
{
if (polyoverlap[i].x * polyoverlap[node.next.ind].y - polyoverlap[node.next.ind].x * polyoverlap[i].y < 0)
{
//Insert point before this element
aaf_indll temp = node.next;
node.next = new aaf_indll();
node.next.ind = i;
node.next.next = temp;
break;
}
}
else
{
//Add point to the end of list
node.next = new aaf_indll();
node.next.ind = i;
node.next.next = null;
break;
}
node = node.next;
}
}//We can leave out the first point because it‘s offset position is going to be (0, 0)
polysortedsize = 0;aaf_indll node2 = root;
aaf_indll temp2;//Add the sorted points to polysorted and clean up memory
while (node2 != null)
{
temp2 = node2;
node2 = node2.next;
if (node2 != null)
polysorted[polysortedsize++] = polyoverlap[node2.ind];}
}private bool PtinConvexPolygon(AafPnt[] p, AafPnt pt)
{
int dir = 0;
int j;//Basically what we are doing is seeing if pt is on the same side of each face of the polygon through cross multiplication
for (int i = 0; i < 4; i++)
{
j = ja[i];
double cross = (p[i].x - pt.x) * (p[j].y - pt.y) - (p[j].x - pt.x) * (p[i].y - pt.y);if (cross == 0)
continue;if (cross > 0)
{
if (dir == -1)
return false;dir = 1;
}
else
{
if (dir == 1)
return false;dir = -1;
}
}
return true;
}
}public delegate bool Aaf_callback(double paraDouble);
struct AafPnt
{
public double x, y;
public AafPnt(double x, double y)
{
this.x = x < 0 ? 0 : x;
this.y = y < 0 ? 0 : y;
}
}class aaf_dblrgbquad
{
public double Red { get; set; }
public double Green { get; set; }
public double Blue { get; set; }
public double Alpha { get; set; }
}
class aaf_indll
{
public aaf_indll next;
public int ind;
}
}这里是调用代码,参数xcorner和ycorner 对应的是矩形4个角的坐标,按顺序是左上、右上、右下、左下。
/// <summary>
/// 透视
/// 2016-12-15
/// </summary>
/// <param name="p_Bitmap"></param>
/// <param name="xcorner"></param>
/// <param name="ycorner"></param>
/// <returns></returns>
public static Bitmap PrepImage(Bitmap p_Bitmap, double[] xcorner, double[] ycorner)
{
Perspective pimg = new Perspective(p_Bitmap, xcorner, ycorner);
return pimg.CreateTransform();
}如果在窗体上直接打印,记得加上 下面这句代码
g.Clear(this.BackColor);
目前代码优化到此,不排除我还会继续优化,之后会再贴出来与大家分享!也很感谢猴健居士。
C# 透视效果
时间: 2024-08-01 07:39:41
C# 透视效果的相关文章
弹性菜单 加了 透视效果 运动 小案例
/**弹性菜单 加了透视效果,类似滚动歌词的效果 *运动过程中,背景色和颜色都会改变,实现原理见 滚动歌词篇 * 小bug 当设置字体为 微软雅黑时,运动过程中菜单中的字体会有小抖动,修复方法,left值不能直接赋值,先存起来**/ 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" 2 "http://www.w3.org/TR/html4/strict.dtd"> 3 4 <html xmln
Windows Phone开发(19):三维透视效果
三维效果也可以叫透视效果,所以,我干脆叫三维透视效果.理论知识少讲,直接用例开场吧,因为这个三维效果其实很简单,比上一节中的变换更省事,不信?一起来做一做练习吧. 练习一:把对象沿Y轴旋转45度. 默认情况下,旋转中心都是在中心位置上,如Y轴中心.而至于旋转的角度,哪个方向是正值,哪个方向是负值,嘿,你自己动手试一下就知道了,我相信你能理解的,不然,Hello Kitty嘲笑你的. 好的,由于是第一个练习,我们先来说说X.Y.Z三个坐标轴到底在哪,X轴不用说了,就横着放的那个了,Y轴呢,当然是竖
【Win 10 应用开发】透视效果
所谓透视效果,就是在平面坐标空间上模拟出“好像”三维的效果.要是老周没有记错的话,以前在写WP8相关的内容时写过,UWP中的透视方法也保留了以前的Do法,其实这玩意儿是从 Silverlight 沿袭下来的.不过,UWP添加了一些三维变换相关的东东,这个老周以后会跟大伙儿聊,今天咱们还是先了解透视效果. UiElement这个可视化对象的公共基类公开了一个Projection属性,可以用于设置对象的透视参数,它的类型是 Projection,可是,Projection 是抽象类,是不能拿来用的,
【UWP通用应用开发】编辑文本、绘制图形、3D透视效果及绘制时钟实战
编辑文本及键盘输入 相信大家都会使用TextBox,但如果要让文本在TextBox中换行该怎么做呢?将TextWrapping属性设置为Wrap,将AcceptsReturn属性设置为True就好咯. PasswordBox很明显就是一个密码框了,和其他的控件相比其有2个特殊之处,一个是其可以用MaxLength来控制最大的长度,一个是用PasswordChanged来捕捉密码的改名.显然比如QQ密码的MaxLength就是16位了,而PasswordChanged可以用来监测比如用户设置的密码
XAML UI 的3D 透视效果
<StackPanel Margin="35" Background="Gray"> <StackPanel.Projection> <PlaneProjection RotationX="-35" RotationY="-35" RotationZ="15"/> </StackPanel.Projection
【转载】如何正确使用cnblogsCSS定制
转自:http://www.cnblogs.com/rvalue/p/7265386.html 用过cnblogs的估计都知道cnblogs提供了相对比较开放的个性化选项,其中最为突出的估计就是页面CSS定制了.但是没学过Web前端的人可能并不会用这个东西... 所以我打算在此分享一些定制CSS过程中使用的奇技淫巧一些方法来帮助大家定制blog qwq 以后如果想到新的主意或者更好的表述的话估计还会回来更新一波...本博文不定期更新吧w 博主并非专业Web开发,所以可能有些语言不够严谨或者不够准
CSS3略谈(中二)
一.3D转换:三维变换使用基于二维变换的相同属性,可以让我们基于三个坐标方向对元素进行变换.和二维变形一样,三维变形可以使用transform属性来设置 1. 3D移动 l 方法:translate3d(x,y,z) 使元素在这三个纬度中移动,也可以分开写,如:translateX(length),translateY(length), translateZ(length) l 示例: div:hover{ /*Y轴移动+100px*/ /*transform:translateY(10
使用CSS3实现一个3D相册
CSS3系列我已经写过两篇文章,感兴趣的同学可以先看一下CSS3初体验之奇技淫巧,CSS3 3D立方体效果-transform也不过如此 第一篇主要列出了一些常用或经典的CSS3技巧和方法:第二篇是一个用CSS3实现的立方体实例,详细讲解了3D旋转和transform等属性. 本文再来利用CSS3属性来编写一个实例,话不多说,先直接看看效果.3D相册实例DEMO 因为前面已经讲解过一些属性的用法,此篇文章不再赘述,只记录这个实例的编码过程.项目代码已上传至github,项目代码github地址
纯CSS3实现的一些酷炫效果
纯CSS3实现的一些酷炫效果 之前在网上看到一些用纯CSS3实现的酷炫效果,以为实现起来比较困难,于是想看看具体是怎么实现的. 一.笑脸猫动画 实现效果如下: 这个实现起来确实比较麻烦,很多地方需要花时间,有耐心地调整. 1.先看下页面结构: <body> <div class="container"> <!-- 脸 --> <div class="face"> <!-- 头发 --> <div cl