Margoo

...?

用 EasyX 画一个海报(使用 Direct2D 进行抗锯齿绘图) 铜牌收录

前言

今天是我写代码的第六周年,想干什么来纪念一下,想到初学的时候用的就是 EasyX 这个库入门,便打算用 EasyX 给自己画一个六周年纪念海报,六年时间一眨眼就过去了,真让人感叹光影似箭~ 正好赶上今天比较空,抽了点时间做了这个海报,做的比较随意,但也当作是一个六年以来的纪念吧。

想起六年前第一次写代码的时候,还是用的一个在线代码运行器跑起了一个 Hello World,自此开始了我的计算机不归路,想起六年前的那个夜晚,和曾经的自己不禁让人感到"物是人非事事休,欲语泪先流”。

运行代码后,会创建一个高八百宽一千的窗口并展示海报。

运行截图

海报预览图

代码

/**
 *  作	 者:Margoo
 *  邮	 箱:1683691371@qq.com
 *  编写环境:CLion NOVA(Preview) + MSVC17.7.0 + EasyX_20220901
 *  编写日期:2023/12/2
 */

/**
 * 强制使用 UNICODE 字符集
 */
#define UNICODE
#define _UNICODE

#include <graphics.h>
#include <conio.h>
#include <d2d1.h>
#include <cmath>

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "MSIMG32.LIB")

constexpr auto Width            = 800;
constexpr auto Height           = 1000;
constexpr auto HalfWidth     = Width / 2;
constexpr auto HalfHeight    = Height / 2;

/**
 * \brief D2D 相关变量
 */

ID2D1Factory*           Facotry;
ID2D1SolidColorBrush*   WhiteBrush;
ID2D1DCRenderTarget*    RenderTarget;

/**
 * \brief 生成一个旋转点
 */
inline D2D_POINT_2F RotatePoint(const int& X, const int& Y, const float& Degree)
{
    if (Degree != 0)
    {
        const auto& DX          = static_cast<float>(X - HalfWidth);
        const auto& DY          = static_cast<float>(Y - HalfHeight);
        const auto RotatedX = DX * std::cos(Degree) - DY * std::sin(Degree);
        const auto RotatedY = DX * std::sin(Degree) + DY * std::cos(Degree);

        return D2D_POINT_2F{
            .x = static_cast<float>(HalfWidth + RotatedX), .y = static_cast<float>(HalfHeight + RotatedY)
        };
    }
    else
    {
        return D2D_POINT_2F{.x = static_cast<float>(X), .y = static_cast<float>(Y)};
    }
}

/**
 * \brief 画一个被旋转过的正方形
 */
void DrawRectangle(const int& Radius, const float& Degree)
{
    const D2D_POINT_2F Points[] = {
        RotatePoint(HalfWidth - Radius, HalfHeight + Radius, Degree),
        RotatePoint(HalfWidth + Radius, HalfHeight + Radius, Degree),
        RotatePoint(HalfWidth + Radius, HalfHeight - Radius, Degree),
        RotatePoint(HalfWidth - Radius, HalfHeight - Radius, Degree),
        RotatePoint(HalfWidth - Radius, HalfHeight + Radius, Degree)
    };

    for (int Count = 0; Count < 4; ++Count)
    {
        RenderTarget->DrawLine(Points[Count], Points[Count + 1], WhiteBrush, 1.5f);
    }
}

/**
 * \brief 初始化画布
 */
void InitLayout()
{
    initgraph(Width, Height);
}

/**
 * \brief 画一层旋转正方形
 */
void DrawLayout(float Degree, const int& MinSize)
{
    for (int Count = Width + 100; Count >= MinSize; Count -= 4)
    {
        DrawRectangle(Count, Degree);

        Degree += 0.015;
    }
}

/**
 * \brief 初始化 D2D 环境
 */
void InitD2D()
{
    const HRESULT ResultHandle = D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &Facotry
    );
    const auto Property = D2D1::RenderTargetProperties(
        D2D1_RENDER_TARGET_TYPE::D2D1_RENDER_TARGET_TYPE_HARDWARE,
        D2D1::PixelFormat(
            DXGI_FORMAT_B8G8R8A8_UNORM,
            D2D1_ALPHA_MODE_IGNORE
        ), 0.0, 0.0, D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE, D2D1_FEATURE_LEVEL_DEFAULT
    );

    HRESULT Result = Facotry->CreateDCRenderTarget(
        &Property,
        &RenderTarget
    );

    if (FAILED(Result) || FAILED(ResultHandle))
    {
        printf("D2D Facotry Created Failed\n");
        MessageBox(GetHWnd(), L"Failed to create D2D factory, program will exit!", L"ERROR", MB_OK + 16);

        exit(-1);
    }

    Result = RenderTarget->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::White),
        &WhiteBrush
    );

    if (FAILED(Result))
    {
        printf("D2D Brush Failed to Create\n");

        MessageBox(GetHWnd(), L"D2D Brush Failed to Create, program will exit!", L"ERROR", MB_OK + 16);

        exit(-1);
    }

    RenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);

    constexpr RECT Rect = {0, 0, Width, Height};
    Result = RenderTarget->BindDC(GetImageHDC(GetWorkingImage()), &Rect);

    if (FAILED(Result))
    {
        printf("D2D Brush Failed to Bind EasyX DC\n");

        MessageBox(GetHWnd(), L"D2D Brush Failed to Bind EasyX DC, program will exit!", L"ERROR", MB_OK + 16);

        exit(-1);
    }
}

/**
 * \brief 生成矩形旋转图片
 */
IMAGE* RectangleImage()
{
    auto* RectangleImage = new IMAGE(Width, Height);
    SetWorkingImage(RectangleImage);

    InitD2D();

    RenderTarget->BeginDraw();
    RenderTarget->Clear(D2D_COLOR_F(D3DCOLORVALUE{.r = 0.2, .g = 0.2, .b = 0.1, .a = 1}));

    DrawLayout(0.32, 60);
    DrawLayout(4.2, 60);

    RenderTarget->EndDraw();

    SetWorkingImage();

    return RectangleImage;
}

/**
 * \brief 生成渐变图片(线性插值)
 */
IMAGE* BackPicture()
{
    auto* RectangleImage = new IMAGE(Width, Height);
    SetWorkingImage(RectangleImage);

    const auto Buffer = GetImageBuffer(RectangleImage);

    constexpr double RDiff = 151 - 67;
    constexpr double GDiff = 8 - 203;
    constexpr double BDiff = 204 - 255;

    for (int Y = 0; Y < Height; ++Y)
    {
        const double Percent = static_cast<float>(Y) / Height;
        const COLORREF LineColor = BGR(RGB(67 + RDiff * Percent, 203 + GDiff * Percent, 255 + BDiff * Percent));
        for (int X = 0; X < Width; ++X)
        {
            Buffer[Y * Width + X] = LineColor;
        }
    }

    SetWorkingImage();

    return RectangleImage;
}

/**
 * \brief 正片叠底操作
 */
void LayerOverlay(IMAGE* Source, IMAGE* Target)
{
    const DWORD* SourceBuffer = GetImageBuffer(Source);
    DWORD* TargetBuffer = GetImageBuffer(Target);

    for (int Count = 0; Count < Width * Height; ++Count)
    {
        DWORD R         = ((TargetBuffer[Count] & 0xff0000) >> 16);
        DWORD G         = ((TargetBuffer[Count] & 0xff00) >> 8);
        DWORD B         = (TargetBuffer[Count] & 0xff);
        const DWORD SR  = ((SourceBuffer[Count] & 0xff0000) >> 16);
        const DWORD SG  = ((SourceBuffer[Count] & 0xff00) >> 8);
        const DWORD SB  = (SourceBuffer[Count] & 0xff);

        R = (R * SR) / 255;
        G = (G * SG) / 255;
        B = (B * SB) / 255;

        if (RGB(R, G, B) != BLACK)
        {
            TargetBuffer[Count] = BGR(RGB(R, G, B));
        }
    }
}

/**
 * \brief 输出文字
 */
void PrintText()
{
    setbkmode(TRANSPARENT);

    LOGFONT Font;

    gettextstyle(&Font);

    _tcscpy_s(Font.lfFaceName, L"Microsoft YaHei");
    Font.lfQuality = PROOF_QUALITY;
    Font.lfHeight = 30;

    settextcolor(RGB(240, 240, 240));

    settextstyle(&Font);

    outtextxy(44, 834, L"FROM 2017 TO 2023");

    Font.lfHeight = 90;
    Font.lfWeight = FW_BLACK;

    settextstyle(&Font);

    outtextxy(44, 754, L"6TH CODING YEAR!");

    Font.lfHeight = 29;
    Font.lfWeight = FW_LIGHT;

    settextstyle(&Font);

    outtextxy(HalfWidth - textwidth(L"- Keep Moving  -") / 2, 924, L"- Keep Moving -");
}

/**
 * \brief 画出海报整体
 */
void PrintPoster()
{
    const auto Rectangle = RectangleImage();
    const auto BackgroundImage = BackPicture();
    LayerOverlay(BackgroundImage, Rectangle);

    putimage(0, 0, Rectangle);

    PrintText();
}

int main()
{
    // 欲买桂花同载酒,终不似,少年游
    InitLayout();
    PrintPoster();

    _getch();

    return 0;
}