使用ddraw在视频上画字和画框(使用ddraw添加水印)

在视频上叠加字符或者画框(或者说添加水印)的方法有很多种,下面列出3种:

  • 1.将我们要添加的水印与视频数据进行融合;
  • 2.使用D3D;
  • 3.使用ddraw。
    这三种方式我都会进行一个简单的描述,本文先说说使用ddraw的方式。
    先参考一下https://blog.csdn.net/ww506772362/article/details/51034549#commentsedit一文说的很好。
    需要提醒一下的是,本文下方有个评论,非常有用。

我们如果是在在RGB视频上画图(直线,矩形等),一般采用双缓冲区继续,使用内存MemoryDC,来实现画的图形在视频上显示不闪烁的功能,但是我们知道用RGB显示视频都是使用GDI进行渲染,这样很耗CPU,那么我们能不能在YUV上进行视频渲染呢,答案是肯定的,使用ddraw直接显示yuv就ok了,可以支持yuv422和yuv420的直接使用显卡显示,不耗CPU,但是我们在使用ddraw显示时,然后在配合GDI画图(直线或矩形等),画的图形是闪烁的,原因是我们在ddraw直接显示yuv视频时,使用的是离屏表面的方法,将yuv数据拷贝到离屏表面,然后在blt到主表面,这样用gdi画图时,和视频刷新不同步,造成闪烁,那么我们怎么解决该问题呢?方法如下:
新增加一个离屏表面,我们定义成osd离屏表面吧,我们将yuv数据拷贝到离屏表面后,在将该离屏表面blt到osd离屏表面,然后在osd离屏表面上画直线或矩形,画完后在blt到主表面,这样画的图形就不会闪烁了。

这里说说上面链接中的源码,有特定情况下可能会有异常:

  • 1、当我们视频的分辨率与电脑显示器的分辨率不一致时;
  • 2、当播放窗口大小发生改变时;
    对于异常一,我们要求主表面的rect要与离屏表面rect一致,否则,在叠加表面时,会返回0x88760096DDERR_INVALIDRECT
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
m_ddsd.dwWidth = m_rctDest.right;
m_ddsd.dwHeight = m_rctDest.bottom;

hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);

这里就要求m_rctDest要与主表面的一致:

对于异常二,我们要求在窗口rect发生改变时,我们对应的离屏表面rect也要对应做改变。

//创建OSD画图离屏表面
		if (!m_pOsdSurface || m_lastwidth != nwidth || m_lastheight != nheight || m_rctDestLast.right != m_rctDest.right || m_rctDestLast.bottom != m_rctDest.bottom)
		{
			if (m_pOsdSurface)
			{
				m_pOsdSurface->Release();
				m_pOsdSurface = NULL;
			}

			ZeroMemory(&m_ddsd, sizeof(m_ddsd));
			m_ddsd.dwSize = sizeof(m_ddsd);
			m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
			m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
			m_ddsd.dwWidth = m_rctDest.right;
			m_ddsd.dwHeight = m_rctDest.bottom;

			hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);
			if (hr != DD_OK)
			{
				return false;
			}
		}

这里要求增加一个判断,当主表面窗口rect发生改变时,我们需要重新创建离屏表面。
现在我把全部源码放出来:
h文件:

#ifndef _H_DISPLAY_H_
#define _H_DISPLAY_H_

#include "ddraw.h"
#include <time.h>
#include <stdio.h>
#include <string>
class display
{
public:
	display();
public:
	virtual ~display();
public:
	long init(HWND hwnd);
	void uninit();
	long inputsource(unsigned char * src, long nwidth, long nheight, long ntype, long nrate, __int64 npts, bool bOsd=false, std::string sOSD="", int x=0, int y=0, int size = 0, int fontr = 0, int fontg = 0, int fontb = 0, float diaphaneity = 0.0);
	static BOOL WINAPI DDEnumCallBack( GUID *pguid, LPTSTR pszdesc, LPTSTR pszdevicename, LPVOID pcontext, HMONITOR hmonior );
	long createoffscreen(LPDIRECTDRAWSURFACE7 * offscreen, DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype);
	long createddsd(DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype);

	void showOSD(HRESULT ddrval, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity);

	void reinit();
private:
	struct MONITOR_INFO
	{
		LPDIRECTDRAW7 lpDD;				// DirectDraw 对象指针
		HMONITOR hMon;	
	};
	LPDIRECTDRAW7 m_lpddraw;						// DirectDraw 对象指针
	LPDIRECTDRAWSURFACE7 m_lpddsprimary;			// DirectDraw 主表面指针
	LPDIRECTDRAWSURFACE7 m_lpddsoffscr;				// DirectDraw 离屏表面指针
	LPDIRECTDRAWSURFACE7 m_lpddsoffscr2;			// DirectDraw 离屏表面指针
	LPDIRECTDRAWCLIPPER m_lpclipper;
	HWND m_hwnd;
	DDSURFACEDESC2 m_ddsd;
	bool m_init;
	long m_lastwidth;
	long m_lastheight;

	RECT m_rctDest;			// 目标区域
	RECT m_rctSour;			// 源区域
	RECT m_rctDestLast;
	DDBLTFX  m_ddbltfx;
	//osd
	LPDIRECTDRAWSURFACE7 m_pOsdSurface; 			//画图表面
};

#endif

cpp文件:

#include "display.h"

#pragma  comment (lib, "ddraw.lib")
#pragma comment(lib, "dxguid.lib")

display::display() : m_lpddraw(NULL),m_lpddsprimary(NULL), m_lpddsoffscr(NULL),
	m_lpddsoffscr2(NULL), m_pOsdSurface(NULL), m_lpclipper(NULL), m_init(false), m_lastwidth(0), m_lastheight(0)
{
	memset(&m_ddsd, 0, sizeof(m_ddsd));
}

display::~display()
{
}

BOOL WINAPI display::DDEnumCallBack( GUID *pguid, LPTSTR pszdesc, LPTSTR pszdevicename, LPVOID pcontext, HMONITOR hmonior )
{
	LPDIRECTDRAW7            lpDD = NULL;				// DirectDraw 对象指针
	MONITOR_INFO *pthis = ( MONITOR_INFO *)pcontext;
	if ( !pthis )
	{
		return FALSE;
	}
	if ( pthis->hMon == hmonior )
	{
		if (DirectDrawCreateEx(pguid, (void**)&lpDD, IID_IDirectDraw7, NULL) != DD_OK)
		{
			OutputDebugStringA("创建lpDD error!\r\n");
		}
		pthis->lpDD = lpDD;
		lpDD = NULL;
	}

	OutputDebugStringA("创建DirectDraw begin\r\n");
	// 创建DirectDraw对象

	return TRUE;
}

void display::reinit()
{
	uninit();
	init(m_hwnd);
}

long display::init(HWND hwnd)
{
	if (m_init)
	{
		return 0;
	}
	m_hwnd = hwnd;
	//MONITOR_INFO mi_lpdd;
	//mi_lpdd.hMon = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
	// 枚举监视器并创建DirectDraw对象
// 	DirectDrawEnumerateExA( display::DDEnumCallBack, 
// 		&mi_lpdd,
// 		DDENUM_ATTACHEDSECONDARYDEVICES | 
// 		DDENUM_DETACHEDSECONDARYDEVICES | 
// 		DDENUM_NONDISPLAYDEVICES
// 		);
// 	m_lpddraw = mi_lpdd.lpDD;
	if(m_lpddraw == NULL)
	{
		if (DirectDrawCreateEx(NULL, (VOID**)&m_lpddraw,IID_IDirectDraw7,NULL) != DD_OK) 
		{
			OutputDebugStringA("display::init 创建DirectDraw NOK\r\n");
		}
		if (!m_lpddraw)
		{
			OutputDebugStringA("display::init 2创建DirectDraw NOK\r\n");
			return -1;
		}
	}
	if (m_lpddraw->SetCooperativeLevel(NULL, DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES | DDSCL_ALLOWREBOOT ) != DD_OK)
	{
		OutputDebugStringA("display::init 创建lpDDxiezuo error!\r\n");
		if (m_lpddraw)
		{
			m_lpddraw->Release();
			m_lpddraw = NULL;
		}
		return -1;
	}
	// 创建主表面
	DDSURFACEDESC2			ddsd;				// DirectDraw 表面描述
	ZeroMemory(&ddsd, sizeof(ddsd));
	ddsd.dwSize = sizeof(ddsd);
	ddsd.dwFlags = DDSD_CAPS;// | DDSD_BACKBUFFERCOUNT;
	ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
	//ddsd.dwBackBufferCount = 1;
	if (m_lpddraw->CreateSurface(&ddsd, &m_lpddsprimary, NULL) != DD_OK)
	{
		OutputDebugStringA("display::init 创建m_lpddsprimary error!\r\n");
		if (m_lpddraw)
		{
			m_lpddraw->Release();
			m_lpddraw = NULL;
		}
		return -1;
	}
	if(m_lpddraw->CreateClipper(0, &m_lpclipper, NULL) != DD_OK)
	{
		if (m_lpddraw)
		{
			m_lpddraw->Release();
			m_lpddraw = NULL;
		}
		OutputDebugStringA("display::init 创建m_lpclipper error!\r\n");
		return -1;
	}

	m_lpclipper->SetHWnd(0, hwnd);
	m_lpddsprimary->SetClipper(m_lpclipper);
	m_init = true;
	return 0;
}

void display::uninit()
{
	if (m_lpddsoffscr)
	{
		m_lpddsoffscr->Release();
		m_lpddsoffscr = NULL;
	}
	if (m_lpddsoffscr2)
	{
		m_lpddsoffscr2->Release();
		m_lpddsoffscr2 = NULL;
	}
	if (m_pOsdSurface)
	{
		m_pOsdSurface->Release();
		m_pOsdSurface = NULL;
	}
	if (m_lpddsprimary)
	{
		m_lpddsprimary->Release();
		m_lpddsprimary = NULL;
	}
	if (m_lpclipper)
	{
		m_lpclipper->Release();
		m_lpclipper = NULL;
	}
	if (m_lpddraw)
	{
		m_lpddraw->Release();
		m_lpddraw = NULL;
	}
	m_init = false;
	m_lastwidth = 0;
	m_lastheight = 0;
}

long display::createddsd(DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype)
{
	ddsd->dwSize = sizeof(*ddsd);
	ddsd->ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY ; //DDSCAPS_OVERLAY DDSCAPS_OFFSCREENPLAIN;
	ddsd->dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;// | DDSD_PITCH | DDSD_LPSURFACE;
	ddsd->dwWidth = nwidth;
	ddsd->dwHeight = nheight;
	ddsd->ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
	ddsd->ddpfPixelFormat.dwFlags = DDPF_FOURCC;
	if ( ntype == 2)
	{
		ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('R','G','B',' ');
		ddsd->ddpfPixelFormat.dwFlags |= DDPF_RGB;
		ddsd->ddpfPixelFormat.dwRGBBitCount = 32;
	}
	else if ( ntype == 3)
	{
		ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','V','1','2');
		ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
		ddsd->ddpfPixelFormat.dwYUVBitCount = 12;
	}
	else if ( ntype == 4)
	{
		ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('U','Y','V','Y');
		ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
		ddsd->ddpfPixelFormat.dwYUVBitCount = 16;
	}
	else if ( ntype == 5)
	{
		ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC(/*'I','4','2','0'*/'Y','V','1','2');
		ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
		ddsd->ddpfPixelFormat.dwYUVBitCount = 12;
	}
	else if ( ntype == 6)
	{
		ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','U','Y','2'); //or YUYV. the same format.
		ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
		ddsd->ddpfPixelFormat.dwYUVBitCount = 16;
	}
	else
	{
		OutputDebugStringA("************LOG_RTM, unsupported pixel format: %d, pcd->m_bdInRender.nType\r\n");
		return -1;
	}
	return 0;
}

long display::createoffscreen(LPDIRECTDRAWSURFACE7 * offscreen, DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype)
{
	__int64 nerror = 0;
	HRESULT result = m_lpddraw->CreateSurface(ddsd, offscreen, NULL);
	if(FAILED(result))
	{
		if (result == DDERR_NODIRECTDRAWHW)
		{
			OutputDebugStringA("************createoffscreen DDERR_NODIRECTDRAWHW*************************\n");
			reinit();
		}
		return -1;
	}
	return 0;
}

long display::inputsource(unsigned char * src, long nwidth, long nheight, long ntype, long nrate, __int64 npts, bool bOsd, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity)
{
	if (!m_init)
	{
		return -1;
	}
	__int64 nerror = 0;
	if (!m_lpddsoffscr || m_lastwidth != nwidth || m_lastheight != nheight)
	{
		if (m_lpddsoffscr)
		{
			m_lpddsoffscr->Release();
			m_lpddsoffscr = NULL;
		}
		nerror = createddsd(&m_ddsd, nwidth, nheight, ntype);
		if (nerror)
		{
			OutputDebugStringA("display::inputsource createddsd error[m_lpddsoffscr equ null]\r\n");
			return -1;
		}
		//创建数据离屏表面
		nerror = createoffscreen(&m_lpddsoffscr, &m_ddsd, nwidth, nheight, ntype);
		if (nerror)
		{
			OutputDebugStringA("display::inputsource createoffscreen error[m_lpddsoffscr equ null]\r\n");
			return -1;
		}
	}
	if (!m_lpddsoffscr)
	{
		OutputDebugStringA("display::inputsource error[m_lpddsoffscr equ null]\r\n");
		return -1;
	}
	HRESULT hr = m_lpddsoffscr->Lock(NULL,&m_ddsd, /*DDLOCK_WAIT |*/ DDLOCK_WRITEONLY,NULL);
	if (hr == DDERR_SURFACELOST) 
	{
        OutputDebugStringA("DDERR_SURFACELOST DDERR_SURFACELOST DDERR_SURFACELOST...1\n");
		hr = m_lpddsoffscr->Restore(); 
		hr = (hr != DD_OK)? hr : m_lpddsoffscr->Lock(NULL,&m_ddsd,/*DDLOCK_WAIT |*/ DDLOCK_WRITEONLY,NULL); 
	}
	if (hr != DD_OK)
	{
		OutputDebugStringA("display::inputsource  Lock error\r\n");
		return -1;
	}

	LPBYTE lpSurf = (LPBYTE)m_ddsd.lpSurface;
	LPBYTE lpSurfV = lpSurf + m_ddsd.dwHeight*m_ddsd.lPitch;
	LPBYTE lpSurfU = lpSurfV + (m_ddsd.dwHeight*m_ddsd.lPitch>>2);
	if (3 == ntype || 5 == ntype)
	{
		LPBYTE lpY = src;
		LPBYTE lpU;
		LPBYTE lpV;
		if (3 == ntype)
		{
			lpV = lpY + nwidth * nheight;
			lpU = lpV + (nwidth * nheight>>2);
		}
		else
		{
			lpU = lpY + nwidth * nheight;
			lpV = lpU + (nwidth * nheight>>2);
		}
		// 填充离屏表面
		unsigned long lddsdHeightHalf = m_ddsd.dwHeight>>1;
		unsigned long lddsdlPitchHalf = m_ddsd.lPitch>>1; 
		unsigned long lWidhtHalf = nwidth>>1;
		for(unsigned long i = 0; i < m_ddsd.dwHeight; i++)
		{
			memcpy(lpSurf, lpY, nwidth);
			lpY += nwidth;
			lpSurf += m_ddsd.lPitch;

			if (i < lddsdHeightHalf)
			{
				memcpy(lpSurfU, lpU, lWidhtHalf);				
				lpU += lWidhtHalf;
				lpSurfU += lddsdlPitchHalf;

				memcpy(lpSurfV, lpV, lWidhtHalf);
				lpV += lWidhtHalf;
				lpSurfV += lddsdlPitchHalf;				
			}
		}
	}

	m_lpddsoffscr->Unlock(NULL);

	m_rctDestLast = m_rctDest;
	m_rctSour.left = 0;
	m_rctSour.top = 0;
	m_rctSour.right = m_ddsd.dwWidth;
	m_rctSour.bottom = m_ddsd.dwHeight;

	GetClientRect(m_hwnd, &m_rctDest);				
	MONITORINFO mi;
	memset(&mi, 0, sizeof(MONITORINFO));
	mi.cbSize = sizeof(MONITORINFO);
	HMONITOR hmon = ::MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
	if (!hmon)
	{
		OutputDebugStringA("display::inputsource MonitorFromWindow error[hmon equ null]\r\n");
		return -1;
	}
	::GetMonitorInfo(hmon, &mi);
	m_rctDest.left -= mi.rcMonitor.left;
	m_rctDest.right -= mi.rcMonitor.left;
	m_rctDest.top -= mi.rcMonitor.top;
	m_rctDest.bottom -= mi.rcMonitor.top;
	ClientToScreen(m_hwnd, (LPPOINT)&m_rctDest.left);
	ClientToScreen(m_hwnd, (LPPOINT)&m_rctDest.right);
		
	memset(&m_ddbltfx, 0, sizeof(m_ddbltfx));
	m_ddbltfx.dwSize = sizeof(m_ddbltfx);
	m_ddbltfx.dwROP = SRCCOPY;

	if (bOsd)
	{
		//创建OSD画图离屏表面
		if (!m_pOsdSurface || m_lastwidth != nwidth || m_lastheight != nheight || m_rctDestLast.right != m_rctDest.right || m_rctDestLast.bottom != m_rctDest.bottom)
		{
			if (m_pOsdSurface)
			{
				m_pOsdSurface->Release();
				m_pOsdSurface = NULL;
			}

			ZeroMemory(&m_ddsd, sizeof(m_ddsd));
			m_ddsd.dwSize = sizeof(m_ddsd);
			m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
			m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
			m_ddsd.dwWidth = m_rctDest.right;
			m_ddsd.dwHeight = m_rctDest.bottom;

			hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);
			if (hr != DD_OK)
			{
				return false;
			}
		}

		//加入Osd离屏表面内容
		HRESULT ddrval;
		ddrval = m_pOsdSurface->Blt(&m_rctDest, m_lpddsoffscr, NULL, DDBLT_WAIT, NULL);
		if (ddrval != DD_OK)
		{
			ddrval = m_lpddsprimary->Blt(&m_rctDest, m_lpddsoffscr, &m_rctSour, DDBLT_WAIT, NULL);
		}
		else
		{
			showOSD(ddrval, sOSD, x, y, size, fontr, fontg, fontb, diaphaneity);
		}
	}
	else
	{
		// Blt到主表面上
		HRESULT hr2 = m_lpddsprimary->Blt(&m_rctDest, m_lpddsoffscr, &m_rctSour, DDBLT_WAIT, &m_ddbltfx);

		if (hr2 == DDERR_SURFACELOST)
		{
			OutputDebugStringA("DDERR_SURFACELOST DDERR_SURFACELOST DDERR_SURFACELOST...\n");
			hr2 = m_lpddsprimary->Restore();
		}
		if (hr2 != DD_OK)
		{
			OutputDebugStringA("display::inputsource Blt error\r\n");
			return -1;
		}
	}
	m_lastwidth = nwidth;
	m_lastheight = nheight;
	return 0;
}

void display::showOSD(HRESULT ddrval, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity)
{
	HDC hDC = NULL;
	ddrval = m_pOsdSurface->GetDC(&hDC);

	if ((ddrval == DD_OK) && (hDC != NULL))
	{
		LOGFONT lf; //定义字体结构
		lf.lfWeight = 400; //字体磅数=10
		lf.lfHeight = size*3/2; //字体高度(旋转后的字体宽度)=56
		lf.lfWidth = size/2; //字体宽度(旋转后的字体高度)=20
		lf.lfUnderline = FALSE; //无下划线
		lf.lfStrikeOut = FALSE; //无删除线
		lf.lfItalic = FALSE; //非斜体
		lf.lfEscapement = 0; //字体显示角度=270°
		lf.lfCharSet = DEFAULT_CHARSET; //使用缺省字符集
		//lf.lfPitchAndFamily = DEFAULT_PITCH;
		strcpy(lf.lfFaceName, "Arial "); //字体名=@system

		HFONT myLogFont = CreateFontIndirect(&lf);
		HGDIOBJ pOldFont = SelectObject(hDC, myLogFont);//选入设备描述表

		//SetTextCharacterExtra(hDC, 0);
		//叠加文字
		SetBkMode(hDC, TRANSPARENT);

		COLORREF rgb = RGB(fontr, fontg, fontb);
		SetTextColor(hDC, rgb);

		int nWidth = m_rctDest.right - m_rctDest.left;
		int nHeight = m_rctDest.bottom - m_rctDest.top;
		x = m_rctDest.left + x % nWidth;
		y = m_rctDest.top + y % nHeight;

		int nPx = (double)(lf.lfWidth) * (sOSD.size());
		int nPy = lf.lfHeight;
		if ((x + nPx) > m_rctDest.right)
		{
			x = m_rctDest.right - nPx;
		}
		if (y + nPy > m_rctDest.bottom)
		{
			y = m_rctDest.bottom - nPy;
		}
		if (y < nPy)
		{
			y = nPy;
		}
		if (x < m_rctDest.left)
		{
			x = m_rctDest.left;
		}
		
		TextOut(hDC, x, y, sOSD.c_str(), sOSD.size());

		SelectObject(hDC, pOldFont); //将myFont从设备环境中分离
		DeleteObject(myLogFont); //删除myLogFont对象

		m_pOsdSurface->ReleaseDC(hDC);
		m_lpddsprimary->Blt(&m_rctDest, m_pOsdSurface, &m_rctDest, DDBLT_WAIT, NULL);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunkingYang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值