• OpenGL -文字


    OpenGL 没有自带专门的字库,因此,要显示文字,就必须依赖操作系统所提供的功
    能
    
    • 1
    • 2

    1. OpenGL 版 “hello world”

    #include 
    // ASCII 字符 总共只有 0 - 127,共 128 种字符
    #define  MAX_CHA 128
    
    void drawString(const char* str)
    {
    	static int isFirstCall = 1;
    	static GLuint lists;
    
    	if(isFirstCall)
    	{
    		// 如果是第一次调用,执行初始化
    		// 为每一个ASCII 字符产生一个显示列表
    		isFirstCall = 0;
    		// 申请 MAX_CHAR 个连续的显示列表编号
    		lists = glGenLists(MAX_CHAR);
    
    		// 把每个字符的绘制命令都装到对应的显示列表中
    		wglUseFontBitmaps(wglGetCurrentDC(),0,MAX_CHAR,lists);		
    	}
    	// 调用每个字符对应的显示列表,绘制每个字符
    	for(;*str != '\0';++str)
    	{
    		glCallList(lists + *str);
    	}
    }
    /*
    	显示列表一旦产生就一直存在,除非调用 glDeleteList 销毁。
    	因此只需要在 第一次调用的时候初始化,以后就可以很方便的调用这些显
    	示列表来绘制字符了
    */ 
    void display(void)
    {
    	// 绘制字符的时候,可以先用 glColor*等指定颜色,然后用 glRasterPos*指定位置,最后调用显示列表来绘制。
    	glClear(GL_COLOR_BUFFER_BIT);
    	glColor3f(1.0f,0.0f,0.0f);
    	glRasterPos2f(0.0f,0.0f);
    	drawString("Hello,World");
    	glutSwapBuffers();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    指定字体

    // 产生显示列表之前 Windows 允许选择字体
    void selectFont(int size,int charset,const char* face)
    {
    	/*
    		size : 字体大小
    		charset :  字符集
    		face : 字体名称
    	*/
    	HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,
    	charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
    	DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);
    	HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);
    	DeleteObject(hOldFont);
    }
    void dispaly(void)
    {
    	selectFont(48,ANSI_CHARSET,"Comic Sans MS");
    	glClear(GL_COLOR_BUFFER_BIT);
    	glColor3f(1.0f,0.0f,0.0f);
    	glRasterPos2f(0.0f,0.0f);
    	drawString("Hello Word!");
    	drawSwapBuffers();
    }
    
    // CreateFont 函数 为 Windows GDI 函数
    // 如果需要在自己的程序中选择字体的话,把 selectFont 函数抄下来,在调用 glutCreateWindow 之后、在调用 wglUseFontBitmaps之前使用 selectFont 函数即可指定字体。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    显示中文

    通常我们在 C 语言里面使用的字符串,如果中英文混合的话,例如“this is 中文字
    符.”,则英文字符只占用一个字节,而中文字符则占用两个字节。用 
    MultiByteToWideChar 函数,可以转化为所有的字符都占两个字节
    
    • 1
    • 2
    • 3
    // 转化的代码如下
    // 计算字符的个数
    // 如果是双字节字符的(比如中文字符),两个字节才算一个字符
    // 否则一个字节算一个字符
    len = 0;
    for(i = 0;str[i] != '\0';++i)
    {
    	if(IsDBCSLeadByte(str[i]))
    	{
    		++i;
    	}
    	++len;
    }
    // 将混合字符转化 为宽字符
    wstring = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
    wstring[len] = L'\0';
    
    // 释放内存
    free(wstring);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    加上前面的 wglUseFontBitmaps 函数,可显示中文字符
    
    • 1
    void drawCNString(const char* str)
    {
    	int len,i;
    	wchar_t* wstring;
    	HDC hDC = wglGetCurrentDC();
    	GLuint list glGenLists(1);
    
    	/*
    		 计算字符的个数
    			如果是双字符的(比如中文),两个字节才算一个字符
    			否则一个字节算一个字符
    	*/	
    	len = 0;
    	for(i = 0;str[i] != '\0';++i)
    	{
    		if(IsDBCSLeadByte(str[i]))
    		{
    			++i;
    		}
    		++len;
    	}
    	// 将混合字符转化为宽字符
    	wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));
    	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);
    	wstring[len] = L'\0';
    
    	// 逐个输出字符
    	for(i=0; i<len; ++i)
    	{
    		wglUseFontBitmapsW(hDC, wstring[i], 1, list);
    		glCallList(list);
    	}
    
    	// 回收所有临时资源
    	free(wstring);
    	glDeleteLists(list, 1);
    }
    /*
    	用了 wglUseFontBitmapsW 函数,而不是 wglUseFontBitmaps。	
    	wglUseFontBitmapsW 是 wglUseFontBitmaps 函数的宽字
    	符版本,它认为字符都占两个字节。因为这里使用了 
    	MultiByteToWideChar,每个字符其实是占两个字节的,所以应该用
    	wglUseFontBitmapsW
    */
    
    void display(void)
    {
    	glClear(GL_COLOR_BUFFER_BIT);
    
    	selectFont(48, ANSI_CHARSET, "Comic Sans MS");
    	glColor3f(1.0f, 0.0f, 0.0f);
    	glRasterPos2f(-0.7f, 0.4f);
    	drawString("Hello, World!");
    
    	selectFont(48, GB2312_CHARSET, "楷体_GB2312");
    	glColor3f(1.0f, 1.0f, 0.0f);
    	glRasterPos2f(-0.7f, -0.1f);
    	drawCNString("当代的中国汉字");
    
    	selectFont(48, DEFAULT_CHARSET, "华文仿宋");
    	glColor3f(0.0f, 1.0f, 0.0f);
    	glRasterPos2f(-0.7f, -0.6f);
    	drawCNString("傳統的中國漢字");
    
    	glutSwapBuffers();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    纹理字体

    把文字放到纹理中可以任意修改字符的大小。
    
    如何把文字放到纹理中?
    	这里不是直接绘制到纹理,而是用简单的办法: 先把汉字绘制出来,成为像素,
    	然后用 glCopyTexImage2D 把像素复制贷纹理。
    
    	glCopyTexImage2D 与 glTexImage2D 的用法类似
    		前者是直接把绘制好的像素复制贷纹理中,后者是从内存传送数据到纹理中。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    // 先把文字绘制好
    glRasterPos2f(XXX,XXX);
    drawCNString("關");
    // 分配纹理编号
    glGenTextures(1,&texID);
    // 指定为当前纹理
    glBindTexture(GL_TEXTURE_2D,texID);
    // 把像素作为纹理数据
    // 将屏幕(0,0) 到 (64,64) 的矩形区域的像素复制到纹理中
    glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,64,64,0);
    // 设置纹理参数
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINIEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LIINEAR);
    // 然后 就可以像使用普通的纹理一样,绘制各种物体时,指定合适的纹理坐标即可。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    glRasterPos2f(XXX,XXX) r如何计算这个显示坐标。
    
    计算文字宽度:
    	Windows 专门提供了一个函数 GetCharABCWidths,它计算一系列连续字符的 
    	ABC 宽度。所谓 ABC宽度,包括了 a, b, c 三个量,a 表示字符左边的空白宽度,
    	b 表示字符实际的宽度,c 表示字符右边的空白宽度,三个宽度值相加得到整个字
    	符所占宽度。
    
    	如果只需要得到总的宽度,可以使用 GetCharWidth32 函数。如果要支持汉字,应
    	该使用宽字符版本,即 GetCharABCWidthsW 和 GetCharWidth32W。在使用前需
    	要用 MultiByteToWideChar 函数,将通常的字符串转化为宽字符串,就像前面的 
    	wglUseFontBitmapsW 那样。
    
    计算高度:
    	指定字体的时候指定大小为 s 的话,所有的字符高度都为 s,只有宽度不同。
    	如果我们使用 glRasterPos2i(-1, -1)从最左下角开始显示字符的话,其实是不能得
    	到完整的字符的:。,因此我们应该设置字体的高度为 2 的整数次方,例如 16, 32, 
    	64,这样用起来就会比较方便。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #define FONT_SIZE 64
    #define TEXTURE_SIZE FONT_SIZE
    /*
    	首先要做的是将字符串转化为宽字符的形式,以便使用 
    	wglUseFontBitmapsW 和 GetCharWidth32W函数。然后设置字体大小,接下
    	来计算字体宽度,计算实际绘制的位置。然后产生显示列表,利用显示列
    	表绘制字符,销毁显示列表。最后分配一个纹理编号,把字符像素复制到
    	纹理中
    */
    GLuint drawChar_To_Texture(const char* s)
    {
    	wchar_t w;
    	HDC hDC = wglGetCurrentDC();
    
    	/*
    		选择字体字号、颜色
    		不指定字体名字,操作系统提供默认字体
    		设置颜色为白色
    	*/
    	selectFont(FONT_SIZE, DEFAULT_CHARSET, "");
    	glColor3f(1.0f, 1.0f, 1.0f);
    
    	// 转化为宽字符
    	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, s, 2, &w, 1);
    
    	// 计算绘制的位置
    	{
    		int width, x, y;
    		GetCharWidth32W(hDC, w, w, &width); // 取得字符的宽度
    		x = (TEXTURE_SIZE - width) / 2;
    		y = FONT_SIZE / 8;
    		glWindowPos2iARB(x, y); // 一个扩展函数
    	}
    
    	// 绘制字符
    	// 绘制前应该将各种可能影响字符颜色的效果关闭
    	// 以保证能够绘制出白色的字符
    	{
    		GLuint list = glGenLists(1);
    		glDisable(GL_DEPTH_TEST);
    		glDisable(GL_LIGHTING);
    		glDisable(GL_FOG);
    		glDisable(GL_TEXTURE_2D);
    		wglUseFontBitmaps(hDC, w, 1, list);
    		glCallList(list);
    		glDeleteLists(list, 1);
    	}
    	// 复制字符像素到纹理
    	// 注意纹理的格式
    	// 不使用通常的 GL_RGBA,而使用 GL_LUMINANCE4
    	// 因为字符本来只有一种颜色,使用 GL_RGBA 浪费了存储空间
    	// GL_RGBA 可能占 16 位或者 32 位,而 GL_LUMINANCE4 只占 4 位
    	{
    	GLuint texID;
    	glGenTextures(1, &texID);
    	glBindTexture(GL_TEXTURE_2D, texID);
    	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE4,0,0,TEXTURE_SIZE, TEXTURE_SIZE, 0);
    	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    	return texID;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    纹理字体应用

    1. 用多个四边形(实际上是矩形)连接起来,制作飘动的效果
    2. 使用光照,计算法线向量
    3. 把纹理融合进去
    
    • 1
    • 2
    • 3
    #define MIN_X (-0.5f)
    #define MAX_X (0.5f)
    #define MIN_Y (-0.5f)
    #define MAX_Y (0.5f)
    #define SEGS ((int)((MAX_X - MIN_X) * (512/2)))
    #define RANGE (0.05f)
    #define CIRCLES (2.0f)
    #define SPEED (5.0f)
    #define PI (3.1415926f)
    /*
    	SEGS 表示分段数,分段越多则显示越细致,这里定义成了与旗帜宽度相关的量。
    	RANGE 表示摆动幅度
    	CIRCLES 表示一面旗帜会出现多少个波峰,
    	SPEED 表示摆动速度
    	这三个量分别与公式 z = sin(t * c1 + c2) * c3 + c4 中的 c3, c2, c1 成正比。
    	常量 PI,表示圆周率。	
    */
    #include 
    // theta 是一个随时间变化的量
    GLfloat theta = 0.0f;
    
    // 绘制一面旗帜
    void draw(void)
    {
    	int i;
    	// 每绘制一段,坐标 x 应该增加的量
    	const GLfloat x_inc = (MAX_X - MIN_X) / SEGS;
    	// 每绘制一段,纹理坐标 s 应该增加的量
    	const GLfloat t_inc = 1.0f / SEGS;
    	// 每绘制一段,常数 theta 应该增加的量
    	const GLfloat theta_inc = 2 * PI * CIRCLES / SEGS;
    	// 用 GL_QUAD_STRIP 来绘制相连的四边形
    	glBegin(GL_QUAD_STRIP);
    	
    	for(i = 0;i <= SEGS;++i)
    	{
    		// 按照 z = sin(t * c1 + c2) * c3 + c4 的公式计算 z 坐标
    		const GLfloat z = RANGE * sin(i*theta_inc + theta);
    		// 一段只需要指定两个点
    		// 第三个点其实是下一段的第一个点
    		// 之所以使用三个点,是为了构成一个平面
    		// 便于计算法线向量
    		const GLfloat
    			v1[] = {i*x_inc + MIN_X, MAX_Y, z},
    			v2[] = {i*x_inc + MIN_X, MIN_Y, z},
    			v3[] = {
    				(i+1)*x_inc + MIN_X,
    				MAX_Y,
    				RANGE * sin((i+1)*theta_inc + theta)
    			};
    		// 调用一个函数来计算法线向量
    		setNormal(v1, v2, v3);
    		// 设置合适的纹理坐标和顶点坐标
    		glTexCoord2f(i*t_inc, 1.0f);
    		glVertex3fv(v1);
    		glTexCoord2f(i*t_inc, 0.0f);
    		glVertex3fv(v2);
    	}
    	glEnd();
    }
    
    // 系统空闲时调用
    // 增加 theta 的值,然后重新绘制
    void idle(void) 
    {
    	theta += (SPEED * PI / 180.0f);
    	glutPostRedisplay();
    }
    
    //因为要使用光照,法线向量是不可少的。这里我们通过不共线的三个点来得到三个点所在平面的法线向量
    /*
    	设置法线向量
    	三个不在同一直线上的点可以确定一个平面
    	先计算这个平面的法线向量,然后指定到 OpenGL
    */
    void setNormal(const GLfloat v1[3],const GLfloat v2[3],const GLfloat v3[3])
    {
    	// 首先根据三个点坐标,相减计算出两个向量
    	const GLfloat s1[] = {
    		v2[0]-v1[0], 
    		v2[1]-v1[1], 
    		v2[2]-v1[2]
    	};
    	const GLfloat s2[] = {
    		v3[0]-v1[0], 
    		v3[1]-v1[1], 
    		v3[2]-v1[2]
    	};
    	// 两个向量叉乘得到法线向量的方向
    	GLfloat n[] = {
    		s1[1]*s2[2] - s1[2]*s2[1],
    		s1[2]*s2[0] - s1[0]*s2[2],
    		s1[0]*s2[1] - s1[1]*s2[0]
    	};
    	// 把法线向量缩放至单位长度
    	GLfloat abs = sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]);
    	n[0] /= abs;
    	n[1] /= abs;
    	n[2] /= abs;
    
    	// 指定到 OpenGL
    	glNormal3fv(n);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    缓冲机制

    改进缓冲机制性能,应该使用更高效的置换算法。
    
    • 1
    #include "ctbuf.h"
    void display(void) 
    {
    	static int isFirstCall = 1;
    	if( isFirstCall )
    	{
    		isFirstCall = 0;
    		ctbuf_init(32, 256, "黑体");
    	}
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    	glEnable(GL_TEXTURE_2D);
    	glPushMatrix();
    	glTranslatef(-1.0f, 0.0f, 0.0f);
    	ctbuf_drawString("美好明天就要到来", 0.1f, 0.15f);
    	glTranslatef(0.0f, -0.15f, 0.0f);
    	ctbuf_drawString("Best is yet to come", 0.1f, 0.15f);
    	glPopMatrix();
    
    	glutSwapBuffers();
    }
    
    const char* g_string ="《合金装备》(Metal Gear Solid)结尾曲歌词\n因为。。。。。。。。 \n美好即将到来\n"
    textarea_t* p_textarea = NULL;
    
    void display(void)
    {
    	static int isFirstCall = 1;
    
    	if( isFirstCall )
    	{
    		isFirstCall = 0;
    		ctbuf_init(24, 256, "隶书");
    		p_textarea = ta_create(-0.7f, -0.5f, 0.7f, 0.5f,20, 10, g_string);
    		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    	}
    	glClear(GL_COLOR_BUFFER_BIT);
    
    	// 显示歌词文字
    	glEnable(GL_TEXTURE_2D);
    	ta_display(p_textarea);
    
    	// 用半透明的效果显示一个方框
    	// 这个框是实际需要显示的范围
    	glEnable(GL_BLEND);
    	glDisable(GL_TEXTURE_2D);
    	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    	glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
    	glRectf(-0.7f, -0.5f, 0.7f, 0.5f);
    	glDisable(GL_BLEND);
    
    	// 显示一些帮助信息
    	glEnable(GL_TEXTURE_2D);
    	glPushMatrix();
    	glTranslatef(-1.0f, 0.9f, 0.0f);
    	ctbuf_drawString("歌词显示程序", 0.1f, 0.1f);
    	glTranslatef(0.0f, -0.1f, 0.0f);
    	ctbuf_drawString("按 W/S 键实现上、下翻页", 0.1f, 0.1f);
    	glTranslatef(0.0f, -0.1f, 0.0f);
    	ctbuf_drawString("按 ESC 退出", 0.1f, 0.1f);
    	glPopMatrix();
    
    	glutSwapBuffers();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
  • 相关阅读:
    shardingsphere做了读写分离做了主从配置脱敏无效分析
    ESP8266-Arduino编程实例-MQ-4气体传感器驱动
    python-后门脚本编写
    【小程序项目开发--京东商城】uni-app之自定义搜索组件(上)-- 组件UI
    智慧工地云平台源码 人工智能AI+多系统集成+智能预警平台源码
    面试常问:HTTPS与HTTP的区别
    SpringBoot 整合 Elasticsearch (超详细)
    Python中的@dataclass装饰器
    32.同步FIFO-IP核的调用
    SpringCache整合Redis实现项目缓存解决方案
  • 原文地址:https://blog.csdn.net/weixin_45054115/article/details/126806152