vtk显示mpr影像(QVTKOpenGLNativeWidget)

5 篇文章 1 订阅
该代码示例展示了如何利用VTK库在C++中显示DICOM图像,支持鼠标滚轮切换图像层。通过QVTKOpenGLNativeWidget控件,实现了3D视图和多平面重建(MPR)视图。同时,定义了一个自定义的交互风格类AxialImageStyle,用于处理鼠标滚轮事件,改变切片显示。
摘要由CSDN通过智能技术生成

该项目是自己在学习VTK时尝试显示dicom,自己对vtk接口理解不是很透彻,目前只能做到显示图像和鼠标滚轮切换层,后期如果有项目锻炼,加深理解后再修改,本文使用的是QVTKOpenGLNativeWidget控件进行显示图像,只列举主要的代码,其他的部分需要自己根据自己的工程情况进行添加界面
#pragma once

#include <QtWidgets/QWidget>
#include
#include “QVTKOpenGLNativeWidget.h”
#include “CommonData.h”
#include “ui_VtkOpenGLWidget.h”
#include “ImageStyleInteractor.h”

class VtkOpenGLWidget : public QVTKOpenGLNativeWidget
{
Q_OBJECT

public:
VtkOpenGLWidget(QWidget* parent = nullptr);
~VtkOpenGLWidget();

void setImageData(vtkSmartPointer<vtkImageData> pImageData);

void setCurrentViewType(View_Type type);

void setName(const char* name);

void setUID(const char* uid);

void setStudyID(const char* studyID);

private:
void show3DImage(/vtkSmartPointer pImageData/);

void initView();

void showMPRImage();

void setWindowLevel(vtkSmartPointer<vtkImageMapToWindowLevelColors>& colors, int min, int max);

void showImageInfo();

void showSlice();

private:
Ui::VtkOpenGLWidgetClass ui;

View_Type m_trViewType;

vtkSmartPointer<vtkOpenGLRenderer> m_pRenderer;

vtkSmartPointer<vtkGenericOpenGLRenderWindow> m_pRenderWindow;

vtkSmartPointer<vtkImageReslice> m_pImageReslice;

vtkSmartPointer<vtkImageData> m_pImageData;

vtkSmartPointer<AxialImageStyle> m_pImageInteractor;

vtkSmartPointer<vtkActor2D> m_pSliceActor;

double m_dAxial[16] = { 1, 0, 0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1 };
double m_dCornal[16] = { 1, 0, 0, 0,
                         0, 0, -1, 0,
                         0, 1, 0, 0,
                         0, 0, 0, 1 };
double m_dSagittal[16] = { 0, 0, 1,0,
                           1, 0, 0,0,
                           0, 1, 0,0,
                           0, 0, 0,1 };

vtkSmartPointer<vtkMatrix4x4> m_matAxial;
vtkSmartPointer<vtkMatrix4x4> m_matCornal;
vtkSmartPointer<vtkMatrix4x4> m_matSigittal;

char* m_pName;

char* m_pUID;

char* m_pStudyID;

std::function<void()> m_pCallBack;

};

#include “VtkOpenGLWidget.h”
#include “ImageStyleInteractor.h”
#include
#include

VtkOpenGLWidget::VtkOpenGLWidget( QWidget *parent)
QVTKOpenGLNativeWidget(parent)
, m_pImageData(nullptr)
, m_pRenderer(nullptr)
, m_pSliceActor(nullptr)
{
ui.setupUi(this);
this->initView();
}

VtkOpenGLWidget::~VtkOpenGLWidget()
{
}

void VtkOpenGLWidget::setCurrentViewType(View_Type type)
{
m_trViewType = type;
}

void VtkOpenGLWidget::initView()
{
/vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(8);
QSurfaceFormat::setDefaultFormat(QVTKOpenGLWidget::defaultFormat());
/

m_pRenderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
this->SetRenderWindow(m_pRenderWindow);

if (m_pRenderer == nullptr)
    m_pRenderer = vtkSmartPointer<vtkOpenGLRenderer>::New();

if (m_pSliceActor == nullptr)
    m_pSliceActor = vtkSmartPointer<vtkActor2D>::New();

m_pCallBack = std::bind(&VtkOpenGLWidget::showSlice, this);

}

void VtkOpenGLWidget::setImageData(vtkSmartPointer pImageData)
{
if (pImageData == nullptr) return;
if(m_pRenderer)
m_pRenderer->RemoveAllViewProps();
m_pImageData = vtkSmartPointer::New();
m_pImageData->DeepCopy(pImageData);
// m_pImageData = pImageData;

int dims[3];
m_pImageData->GetDimensions(dims);

if (m_trViewType == Image3D)
    this->show3DImage();
else
    this->showMPRImage();

}

void VtkOpenGLWidget::show3DImage(/vtkSmartPointer pImageData/)
{
vtkSmartPointer pInteractor = vtkSmartPointer::New();
pInteractor->SetRenderWindow(m_pRenderWindow);
vtkSmartPointer pImageInteractor = vtkSmartPointer::New();
pInteractor->SetInteractorStyle(pImageInteractor);
pInteractor->Initialize();

vtkSmartPointer<vtkOpenGLGPUVolumeRayCastMapper> pVolumeMapper = vtkSmartPointer < vtkOpenGLGPUVolumeRayCastMapper>::New();
pVolumeMapper->SetInputData(m_pImageData);

vtkSmartPointer<vtkVolumeProperty> pVolumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
pVolumeProperty->SetInterpolationTypeToLinear();
pVolumeProperty->ShadeOn();
pVolumeProperty->SetAmbient(0.2);//环境温度系数
pVolumeProperty->SetDiffuse(0.7);//漫反射系数
pVolumeProperty->SetSpecular(0.5);//镜面反射系数

vtkSmartPointer<vtkPiecewiseFunction> pFunction = vtkSmartPointer<vtkPiecewiseFunction>::New();
pFunction->AddPoint(-3024, 0, 0.5, 0.0);
pFunction->AddPoint(-16.0,0,0.49, 0.61);
pFunction->AddPoint(641,0.72,0.5, 0.0);
pFunction->AddPoint(3071,0.71,0.5, 0.0);
pVolumeProperty->SetScalarOpacity(pFunction);

vtkSmartPointer<vtkPiecewiseFunction> pGridFunction = vtkSmartPointer<vtkPiecewiseFunction>::New();
pGridFunction->AddPoint(10, 0.0);
pGridFunction->AddPoint(90, 0.5);
pGridFunction->AddPoint(100, 1.0);
pVolumeProperty->SetGradientOpacity(pGridFunction);

vtkSmartPointer<vtkColorTransferFunction> pColorTransferFunction = vtkSmartPointer<vtkColorTransferFunction>::New();
pColorTransferFunction->AddRGBPoint(-3024, 0, 0, 0, 0.5, 0.0);
pColorTransferFunction->AddRGBPoint(-16, 0.73, 0.25, 0.30, 0.49, .61);
pColorTransferFunction->AddRGBPoint(641, .90, .82, .56, .5, 0.0);
pColorTransferFunction->AddRGBPoint(3071, 1, 1, 1, .5, 0.0);
pVolumeProperty->SetColor(pColorTransferFunction);

vtkSmartPointer<vtkVolume> p3DVolume = vtkSmartPointer<vtkVolume>::New();
p3DVolume->SetMapper(pVolumeMapper);
p3DVolume->SetProperty(pVolumeProperty);


if (m_pRenderer == nullptr)
    m_pRenderer = vtkSmartPointer<vtkOpenGLRenderer>::New();
m_pRenderWindow->AddRenderer(m_pRenderer);
m_pRenderer->AddVolume(p3DVolume);

m_pRenderer->GetActiveCamera()->SetPosition(0, 1, 0);
m_pRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
m_pRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
m_pRenderer->GetActiveCamera()->Zoom(0.8);
m_pRenderer->ResetCamera();
m_pRenderWindow->Render();

}

void VtkOpenGLWidget::showMPRImage()
{
if (m_pImageReslice == nullptr)
m_pImageReslice = vtkSmartPointer::New();

vtkSmartPointer<vtkRenderWindowInteractor> pInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
pInteractor->SetRenderWindow(m_pRenderWindow);
m_pImageInteractor = vtkSmartPointer<AxialImageStyle>::New();
m_pImageInteractor->setViewType(m_trViewType);
m_pImageInteractor->setMouseWheelCallBack(m_pCallBack);
pInteractor->SetInteractorStyle(m_pImageInteractor);
pInteractor->Initialize();

{
    double* spacing = m_pImageData->GetSpacing();
    int* extent = m_pImageData->GetExtent();
    double* origin = m_pImageData->GetOrigin();
    double center[3];
    center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
    center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
    center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);

    m_pImageReslice->SetInputData(m_pImageData);
    if (m_trViewType == ImageAxial)
    {
		m_matAxial = vtkSmartPointer<vtkMatrix4x4>::New();
		m_matAxial->DeepCopy(m_dAxial);
		m_matAxial->SetElement(0, 3, center[0]);
		m_matAxial->SetElement(1, 3, center[1]);
		m_matAxial->SetElement(2, 3, center[2]);
        m_pImageReslice->SetResliceAxes(m_matAxial);
        m_pImageReslice->SetOutputDimensionality(3);
        m_pImageReslice->SetInterpolationModeToLinear();
        m_pImageReslice->Update();
    }
    else if (m_trViewType == ImageCoronal)
    {
		m_matCornal = vtkSmartPointer<vtkMatrix4x4>::New();
		m_matCornal->DeepCopy(m_dCornal);
		m_matCornal->SetElement(0, 3, center[0]);
		m_matCornal->SetElement(1, 3, center[1]);
		m_matCornal->SetElement(2, 3, center[2]);
        m_pImageReslice->SetResliceAxes(m_matCornal);    
        m_pImageReslice->SetOutputDimensionality(3);
        m_pImageReslice->SetInterpolationModeToLinear();
        m_pImageReslice->Update();
    }
    else if (m_trViewType == ImageSagittal)
    {
		m_matSigittal = vtkSmartPointer<vtkMatrix4x4>::New();
		m_matSigittal->DeepCopy(m_dSagittal);
		m_matSigittal->SetElement(0, 3, center[0]);
		m_matSigittal->SetElement(1, 3, center[1]);
		m_matSigittal->SetElement(2, 3, center[2]);
        m_pImageReslice->SetResliceAxes(m_matSigittal); 
    }
    m_pImageReslice->SetOutputDimensionality(3);
    m_pImageReslice->SetInterpolationModeToLinear();
    m_pImageReslice->Update();

    vtkSmartPointer<vtkLookupTable> myLookupTable = vtkSmartPointer<vtkLookupTable>::New();
    myLookupTable->SetRange(0, 1000);
    myLookupTable->SetValueRange(0.0, 1.0);
    myLookupTable->SetSaturationRange(0.0, 0.0);
    myLookupTable->SetRampToLinear();
    myLookupTable->Build();

    vtkSmartPointer<vtkImageMapToWindowLevelColors> myMapToColors = vtkSmartPointer < vtkImageMapToWindowLevelColors > ::New();
    myMapToColors->SetLookupTable(myLookupTable);
    myMapToColors->SetInputConnection(m_pImageReslice->GetOutputPort());
    m_pImageInteractor->setViewImageMapColor(myMapToColors);

    auto al = myMapToColors->GetInputAlgorithm();
    al->UpdateInformation();
    int* temp = al->GetOutputInformation(0)->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
    vtkSmartPointer<vtkImageActor> pImageActor = vtkSmartPointer<vtkImageActor>::New();
    pImageActor->GetMapper()->SetInputConnection(myMapToColors->GetOutputPort());
    //pImageActor->SetInputData(myMapToColors->GetOutput());
    m_pImageInteractor->setViewActor(pImageActor);

    int currentSlice;
    m_pImageInteractor->getCurrentSlice(currentSlice);
    if (this->m_trViewType == ImageCoronal)//ImageCoronal
    {
		pImageActor->SetDisplayExtent(extent[0], extent[1], extent[2], extent[3], currentSlice, currentSlice);
        this->setWindowLevel(myMapToColors, extent[2], extent[3]);
    } 
    else if (this->m_trViewType == ImageAxial)//ImageAxial
    {
		pImageActor->SetDisplayExtent(extent[0], extent[1], currentSlice, currentSlice, extent[4], extent[5]);
        this->setWindowLevel(myMapToColors, extent[4], extent[5]);
    }     
    else if (this->m_trViewType == ImageSagittal)
    {
        pImageActor->SetDisplayExtent(currentSlice, currentSlice, extent[2], extent[3], extent[4], extent[5]);
        this->setWindowLevel(myMapToColors, extent[0], extent[1]);
    }

    pImageActor->ForceOpaqueOn();
    m_pRenderer->AddActor(pImageActor);
    m_pRenderWindow->AddRenderer(m_pRenderer);
	m_pRenderer->GetActiveCamera()->SetFocalPoint(0, 0, 0);
	if (m_trViewType == ImageAxial)//xz
	{
	    m_pRenderer->GetActiveCamera()->SetPosition(0, -1, 0);
	    m_pRenderer->GetActiveCamera()->SetViewUp(0, 0, 1); 
	}
	else if (m_trViewType == ImageCoronal)//xy
	{
	    m_pRenderer->GetActiveCamera()->SetPosition(0, 0, 1);
	    m_pRenderer->GetActiveCamera()->SetViewUp(0, 1, 0);
	}
	else if (m_trViewType == ImageSagittal)//yz
	{
		m_pRenderer->GetActiveCamera()->SetPosition(1, 0, 0);
		m_pRenderer->GetActiveCamera()->SetViewUp(0, 0, 1);
	    m_pRenderer->GetActiveCamera()->SetClippingRange(0.1, 1000);
	}
    m_pRenderer->GetActiveCamera()->OrthogonalizeViewUp();
	m_pRenderer->ResetCamera();
    showImageInfo();
    //showSlice();
    m_pRenderWindow->Render();

    m_pImageInteractor->setRender(m_pRenderer);
}

}

void VtkOpenGLWidget::setName(const char* name)
{
if (name == nullptr) return;
m_pName = (char*)malloc(128 * sizeof(char));
memcpy(m_pName, name, 127);
m_pName[128] = ‘\0’;
}

void VtkOpenGLWidget::setUID(const char* uid)
{
if (uid == nullptr)return;
int s = sizeof(char);
m_pUID = (char*)malloc(256 * sizeof(char));
memcpy(m_pUID, uid, 255);
m_pUID[255] = ‘\0’;
}

void VtkOpenGLWidget::setStudyID(const char* studyID)
{
if (studyID == nullptr)return;
m_pStudyID = (char*)malloc(256 * sizeof(char));
memcpy(m_pStudyID, studyID, 255);
m_pStudyID[256] = ‘\0’;
}

void VtkOpenGLWidget::setWindowLevel(vtkSmartPointer& colors, int min, int max)
{
colors->SetWindow(max - min);
colors->SetLevel(0.5 * (min + max));
}

void VtkOpenGLWidget::showImageInfo()
{
std::stringstream stream;
stream << “name:” << m_pName<< “\n” << “uid:” << m_pUID<<“\n” << “studyID:” << m_pStudyID;

/*vtkUnicodeString pUnicodeString  = vtkUnicodeString::from_utf8(stream.str().c_str());
auto str = pUnicodeString.utf8_str();*/

vtkSmartPointer<vtkTextProperty> pProperty = vtkSmartPointer<vtkTextProperty>::New();
pProperty->SetFontFamilyToCourier();
pProperty->SetFontSize(12);
pProperty->SetJustificationToLeft();
pProperty->SetVerticalJustificationToBottom();

vtkSmartPointer<vtkTextMapper> pMapper = vtkSmartPointer<vtkTextMapper>::New();
pMapper->SetInput(stream.str().c_str());
pMapper->SetTextProperty(pProperty);
vtkSmartPointer<vtkActor2D> pTextActor = vtkSmartPointer<vtkActor2D>::New();
pTextActor->SetMapper(pMapper);
m_pRenderer->AddActor2D(pTextActor);

}

下面是vtkInteractorStyleImage子类,主要用于鼠标交互
class AxialImageStyle : public vtkInteractorStyleImage
{

public:
static AxialImageStyle* New();
vtkTypeMacro(AxialImageStyle, vtkInteractorStyleImage)
void setImageSlice(vtkSmartPointer pImageSlice);

void setViewType(View_Type type);

void setViewActor(vtkSmartPointer<vtkImageActor> pActor);

void setViewImageMapColor(vtkSmartPointer<vtkImageMapToWindowLevelColors> pWindowColor);

int* getImageRange();

void getCurrentSlice(int& slice);

void setRender(vtkSmartPointer<vtkOpenGLRenderer> pRender);

void setMouseWheelCallBack(std::function<void()> pCallBack);

void getSliceRange(int& min, int& max);

protected:
void OnMouseWheelForward()override;
void OnMouseWheelBackward()override;
private:
explicit AxialImageStyle();

void showSlice();

void updateDisplayExtent();

std::array<int, 2> sliceMax();

private:
vtkSmartPointer m_pCurrSlice;

View_Type m_viewType;

vtkSmartPointer<vtkImageActor> m_pActor;

vtkSmartPointer<vtkImageMapToWindowLevelColors> m_pMapColors;

vtkSmartPointer<vtkOpenGLRenderer> m_pRenderer;

vtkSmartPointer<vtkTextActor> m_pSliceActor;

int m_iSlice;

std::function<void()> m_pCallBack;

};

            AxialImageStyle* AxialImageStyle::New()

{
return new AxialImageStyle();
}

AxialImageStyle::AxialImageStyle()
vtkInteractorStyleImage()
, m_pSliceActor(nullptr)
{

}

void AxialImageStyle::setImageSlice(vtkSmartPointer slice)
{
this->m_pCurrSlice = slice;
}

void AxialImageStyle::setViewType(View_Type type)
{
m_viewType = type;
}

void AxialImageStyle::setViewActor(vtkSmartPointer pActor)
{
m_pActor = pActor;
}

void AxialImageStyle::setViewImageMapColor(vtkSmartPointer colors)
{
m_pMapColors = colors;
}

int* AxialImageStyle::getImageRange()
{
vtkAlgorithm* pAlgorithm = m_pMapColors->GetInputAlgorithm();
pAlgorithm->UpdateInformation();
return pAlgorithm->GetOutputInformation(0)->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
}

void AxialImageStyle::getCurrentSlice(int& slice)
{
int* rangeValue = getImageRange();
if (m_viewType == ImageAxial)
{
slice = std::round((rangeValue[5] + rangeValue[4])* 0.5);
m_iSlice = slice;
}
else if (m_viewType == ImageSagittal)
{
slice = std::round((rangeValue[0] + rangeValue[1]) * 0.5);
m_iSlice = slice;
}
else if (m_viewType == ImageCoronal)
{
slice = std::round((rangeValue[2] + rangeValue[3]) * 0.5);
m_iSlice = slice;
}
}

void AxialImageStyle::setRender(vtkSmartPointer pRender)
{
m_pRenderer = pRender;
//showSlice();
}

void AxialImageStyle::setMouseWheelCallBack(std::function<void()> pCallBack)
{
m_pCallBack = pCallBack;
}

void AxialImageStyle::getSliceRange(int& min, int& max)
{
int* rangeValue = getImageRange();
if (m_viewType == ImageAxial)
{
min = rangeValue[4];
max = rangeValue[5];
}
else if (m_viewType == ImageCoronal)
{
min = rangeValue[2];
max = rangeValue[3];
}
else if (m_viewType == ImageSagittal)
{
min = rangeValue[0];
max = rangeValue[1];
}

}

void AxialImageStyle::OnMouseWheelForward()
{
//滚轮放大
/this->FindPokedRenderer(this->Interactor->GetEventPosition()[0], this->Interactor->GetEventPosition()[1]);
if (this->CurrentRenderer == nullptr)return;
this->GrabFocus(this->EventCallbackCommand);
this->StartDolly();
double factor = this->MotionFactor * 0.2 * this->MouseWheelMotionFactor;
this->Dolly(pow(1.1, factor));
this->EndDolly();
this->ReleaseFocus();
/
std::array<int, 2> sliceMinMax = this->sliceMax();
if (this->m_iSlice < sliceMinMax[1])
++this->m_iSlice;
this->updateDisplayExtent();
//if (m_pCallBack) m_pCallBack();
}

void AxialImageStyle::OnMouseWheelBackward()
{
//滚轮缩小
/this->FindPokedRenderer(this->Interactor->GetEventPosition()[0], this->Interactor->GetEventPosition()[1]);
if (this->CurrentRenderer == nullptr)return;
this->GrabFocus(this->EventCallbackCommand);
this->StartDolly();
double factor = this->MotionFactor * -0.2 * this->MouseWheelMotionFactor;
this->Dolly(pow(1.1, factor));
this->EndDolly();
this->ReleaseFocus();
/

std::array<int, 2> sliceMinMax = this->sliceMax();
if (this->m_iSlice > sliceMinMax[0])
	--this->m_iSlice;
this->updateDisplayExtent();
//if (m_pCallBack) m_pCallBack();

}

void AxialImageStyle::updateDisplayExtent()
{
int* range = this->getImageRange();
int min = range[this->m_viewType * 2];
int max = range[this->m_viewType * 2 + 1];
if (this->m_iSlice < min)
this->m_iSlice = min;
else if (this->m_iSlice > max)
this->m_iSlice = max;
switch (this->m_viewType)
{
case View_Type::ImageCoronal:
this->m_pActor->SetDisplayExtent(range[0], range[1], range[2], range[3], this->m_iSlice, this->m_iSlice);
break;
case View_Type::ImageSagittal:
this->m_pActor->SetDisplayExtent(this->m_iSlice, this->m_iSlice, range[2], range[3], range[4], range[5]);
break;
case View_Type::ImageAxial:
this->m_pActor->SetDisplayExtent(range[0], range[1], this->m_iSlice, this->m_iSlice, range[4], range[5]);
break;
default:
break;
}
this->Modified();
this->Interactor->Render();
if (this->m_pRenderer)
{
if (this->GetAutoAdjustCameraClippingRange())
{
this->m_pRenderer->ResetCameraClippingRange();
}
else
{
vtkCamera* cam = this->m_pRenderer->GetActiveCamera();
if (cam)
{
double bounds[6];
this->m_pActor->GetBounds(bounds);
double spos = bounds[this->m_viewType * 2];
double cpos = cam->GetPosition()[this->m_viewType];
double range = fabs(spos - cpos);
double* spacing = m_pMapColors->GetInputAlgorithm()->GetOutputInformation(0)->Get(vtkDataObject::SPACING());
double avg_spacing = (spacing[0] + spacing[1] + spacing[2]) / 3.0;
cam->SetClippingRange(range - avg_spacing * 3.0, range + avg_spacing * 3.0);
}
}
}

}

void AxialImageStyle::showSlice()
{
if (m_pSliceActor == nullptr)
{
m_pSliceActor = vtkSmartPointer::New();
m_pRenderer->AddActor2D(m_pSliceActor);
}

int slice = 0, min = 0, max = 0;
this->getCurrentSlice(slice);
this->getSliceRange(min, max);
std::stringstream stream;
stream << "slice:" << slice << "/" << max;

m_pSliceActor->SetInput(stream.str().c_str());
m_pSliceActor->SetDisplayPosition(50, 50);
m_pSliceActor->GetTextProperty()->SetFontSize(12);

}

std::array<int, 2> AxialImageStyle::sliceMax()
{
vtkAlgorithm* pGroithm = this->m_pMapColors->GetInputAlgorithm();
pGroithm->UpdateInformation();
int* sliceExtent = pGroithm->GetOutputInformation(0)->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
std::array<int, 2> range{ sliceExtent[this->m_viewType * 2], sliceExtent[this->m_viewType * 2 + 1] };
return range;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Pailugou

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

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

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

打赏作者

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

抵扣说明:

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

余额充值