C++实现图像转字符画

这个是我顺手写的玩具,因此有好些bug,例如图像步长、大小不能自适应等等的问题。不过按照这个思路你能拿到很好的结果。个人认为效果也可以,先上效果:

C++实现图像转字符画_第1张图片

效果可以吧。

实现思路很简单,就是按照灰度查表。但是单纯这样是拿不到好结果的——因此我使用拉普拉斯算子的一个变种进行边缘检测,然后以此进行灰度加强,最后再输出图片。完整代码如下,图像编解码使用WIC接口,手写了一个二维卷积,一个图片转灰度图。组合在一起就是结果:

/*
 | Img2Char
 | 文件名称: img2chr.cpp
 | 文件作用: 唯一的源文件
 | 创建日期: 2020-04-16
 | 更新日期: 2020-04-18
 | 开发人员: JuYan
 +----------------------------
 Copyright (C) JuYan, all rights reserved.
 该程序可以把你的图像变成字符画
 WARNING: bug有好几个
*/
#pragma region include和define
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#pragma comment(lib, "Windowscodecs.lib")
#define memalloc(type, num)    (type*)_memalloc(sizeof(type) * (num))
#define BUFF2(p, i, j)       (*((p) + (j) * w + (i)))
#define KERNEL_W             7
#define KERNEL_H             7
#pragma endregion
#pragma region struct定义
enum ImageChannle
{
    Channel_B, Channel_G, Channel_R, Channel_A
};
typedef struct tagImageData
{
    BYTE *rgba;
    int   w, h;
} ImageData;
typedef struct tagGaryImageData
{
    BYTE *dat;                              // 0xff是顶级
    int  *tmp;                              // 在各个处理过程中可能用到的temp数据
    int   w, h;
} GaryImageData;
#pragma endregion
#pragma region 全局变量
IWICImagingFactory *pFactory = NULL;
#pragma endregion
#pragma region 杂项函数
// 打印错误信息
void printmsg(const char *msg, ...)
{
    va_list va;
    va_start(va, msg);
    vfprintf(stderr, msg, va);
    putchar('\n');
    va_end(va);
}
// 分配内存和释放内存
void* _memalloc(size_t sz)
{
    void *p;
    p = malloc(sz);
    if (p == NULL)
    {
        printmsg("Can not allocate %d bytes on heap.", sz);
        assert(0);
        abort();
    }
    return p;
}
void memfree(void *p)
{
    if (p != NULL)
    {
        free(p);
    }
    p = NULL;
}
#pragma endregion 
#pragma region 图片读写
// 释放一个接口
template inline void SafeRelease(T * &p)
{
    if (p)
    {
        p->Release();
    }
    p = NULL;
}
// 读取图片, 得到rgba, 失败返回false
bool LoadImageData(const wchar_t *file, ImageData *res)
{
    bool    ret;
    BYTE   *dat;
    HRESULT hr;
    WICRect rcLock;
    UINT    i, j, k, datSz, dataStride, imgW, imgH;
    int dataStep;
    BYTE   *pImgData;
    IWICBitmap *pBitmap = NULL;
    IWICBitmapLock  *pLock = NULL;
    WICPixelFormatGUID formatGUID;
    IWICBitmapDecoder  *pDecoder = NULL;                                            // 解码器要即时创建和释放
    IWICBitmapFrameDecode *pDecFrame = NULL;
    ret = true;
    res->w = 0;
    res->h = 0;
    res->rgba = NULL;
    hr = pFactory->CreateDecoderFromFilename(file, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &pDecoder);
    if (FAILED(hr))
    {
        printmsg("Can not load image : 0x%x", hr);
        return false;
    }
    hr = pDecoder->GetFrame(0, &pDecFrame);
    if (FAILED(hr))
    {
        ret = false;
        goto err;
    }
    // 载入图像信息
    pDecFrame->GetSize(&imgW, &imgH);
    pDecFrame->GetPixelFormat(&formatGUID);                                         // 取得图像编码
    if (IsEqualGUID(formatGUID, GUID_WICPixelFormat32bppBGRA))
    {
        dataStep = 4;
    }
    else if (IsEqualGUID(formatGUID, GUID_WICPixelFormat24bppBGR))
    {
        dataStep = 3;
    }
    else {
        ret = false;
        printmsg("Unknow image format");
        goto err;
    }
    // 拷贝图像数据
    rcLock.X = 0;
    rcLock.Y = 0;
    rcLock.Width = imgW;
    rcLock.Height = imgH;                                                           // 接下来我们创建位图以得到数据
    hr = pFactory->CreateBitmapFromSource(pDecFrame, WICBitmapCacheOnDemand, &pBitmap);
    if (FAILED(hr))
    {
        ret = false;
        goto err;
    }
    pBitmap->Lock(&rcLock, WICBitmapLockWrite, &pLock);
    pLock->GetStride(&dataStride);                                                  // 取得一行有多少字节
    hr = pLock->GetDataPointer(&datSz, &dat);                                       // 取得图像数据
    if (FAILED(hr))
    {
        ret = false;
        printmsg("Can not get image data from memory.");
        goto err;
    }
    pImgData = memalloc(BYTE, imgW * imgH * sizeof(UINT));                          // 最后, 拷贝像素数据
    for (i = 0, j = 0; i < dataStride * imgH; i += dataStride)
    {
        for (k = 0; k < imgW * dataStep; k += dataStep, j += 4)
        {
            pImgData[j + 0] = dat[i + k + 0];
            pImgData[j + 1] = dat[i + k + 1];
            pImgData[j + 2] = dat[i + k + 2];
            if (dataStep == 4)
            {
                pImgData[j + 3] = dat[i + k + 3];
            }
            else {
                pImgData[j + 3] = 0xff;
            }
        }
    }
    res->w = (int)imgW;
    res->h = (int)imgH;
    res->rgba = pImgData;
err:
    SafeRelease(pLock);
    SafeRelease(pBitmap);
    SafeRelease(pDecFrame);
    SafeRelease(pDecoder);
    return ret;
}
// 保存一段数据, 格式为32位的PNG
bool SaveImageData(const wchar_t *file, const ImageData *dat)
{
    bool ret;
    UINT w, h;
    HRESULT hr;
    IWICStream *pStream = NULL;
    IWICBitmapEncoder *pEncoder = NULL;
    IWICBitmapFrameEncode *pBitmapFrame = NULL;
    WICPixelFormatGUID formatGUID = GUID_WICPixelFormat32bppBGRA;
    ret = true;
    w = (UINT)dat->w;
    h = (UINT)dat->h;
    pFactory->CreateStream(&pStream);
    hr = pStream->InitializeFromFilename(file, GENERIC_WRITE);
    if (FAILED(hr))
    {
        ret = false;
        printmsg("Can not save file");
        goto err;
    }
    hr = pFactory->CreateEncoder(GUID_ContainerFormatPng, NULL, &pEncoder);         // png编码器
    if (FAILED(hr))
    {
        ret = false;
        printmsg("Can not create encoder");
        goto err;
    }
    pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
    hr = pEncoder->CreateNewFrame(&pBitmapFrame, NULL);                             // 创建一个帧
    if (FAILED(hr))
    {
        ret = false;
        goto err;
    }
    pBitmapFrame->Initialize(NULL);
    pBitmapFrame->SetSize(w, h);
    pBitmapFrame->SetPixelFormat(&formatGUID);
    hr = pBitmapFrame->WritePixels(
        h,
        w * sizeof(UINT),
        w * h * sizeof(UINT),
        dat->rgba
    );
    if (FAILED(hr))
    {
        ret = false;
        goto err;
    }
    pBitmapFrame->Commit();                                                         // 提交更改
    pEncoder->Commit();
    pStream->Commit(STGC_DEFAULT);
err:
    SafeRelease(pStream);
    SafeRelease(pBitmapFrame);
    SafeRelease(pEncoder);
    return ret;
}
// 释放ImageData
void ReleaseImageData(ImageData *p)
{
    memfree(p->rgba);
}
#pragma endregion
#pragma region 灰度处理
void CreateGaryImage(const ImageData *src, GaryImageData *res)
{
    BYTE *pres;
    int imgsize, r;
    const BYTE *psrc;
    assert(src->rgba);
    res->w = src->w;
    res->h = src->h;
    imgsize = res->w * res->h;

    res->dat = memalloc(BYTE, imgsize);                                             // 分配内存空间
    res->tmp = memalloc(int,  imgsize);

    pres = res->dat;
    psrc = src->rgba;
    while (imgsize-- > 0)
    {
        if (psrc[Channel_A] != 0)
        {
            r = (int)(psrc[Channel_R] * 0.3 + psrc[Channel_G] * 0.59 + psrc[Channel_B] * 0.11);
        }
        else {
            r = 0xff;                                                               // 完全透明的地方设置为白色
        }
        *pres++ = r > 0xff ? 0xff : r;
        psrc += 4;                                                                  // R G B A 四个通道
    }

}
// 把灰度图转到一个已有的RGBA数据上
void Gary2RGBA(const GaryImageData *src, ImageData *dst)
{
    int imgsize;
    BYTE * pdst;
    const BYTE *psrc;
    assert(src->dat);
    pdst = dst->rgba;
    psrc = src->dat;
    imgsize = src->w * src->h;
    while (imgsize-- > 0)                                                           // 这玩意说白了就是把rgb三个通道设置为灰度值
    {
        pdst[Channel_A] = 0xff;
        pdst[Channel_R] = *psrc;
        pdst[Channel_G] = *psrc;
        pdst[Channel_B] = *psrc;
        pdst += 4;
        psrc++;
    }
}
// 释放灰度图
void ReleaseGaryImage(GaryImageData *p)
{
    if (p->dat != NULL)
    {
        memfree(p->tmp);
        memfree(p->dat);
    }
}
#pragma endregion
#pragma region 拉普拉斯
template int GetPixelValue(T *dat, int x, int y, int maxw, int maxh)
{
    if (x < 0)
        x = 0;
    if (y < 0)
        y = 0;
    if (x >= maxw)
        x = maxw - 1;
    if (y >= maxh)
        y = maxh - 1;

    return (int)*(dat + y * maxw + x);
}
inline int GetPixelValue(const GaryImageData &img, int x, int y)
{
    return GetPixelValue(img.dat, x, y, img.w, img.h);
}
// 对灰度图像进行卷积
void CreateGaryImageCov(const double kernel[KERNEL_W][KERNEL_H], const GaryImageData &src, GaryImageData *dst)
{
    int x, y;
    int m, n, w;
    int imgsize, cx, cy;
    double sum;
    w = src.w;
    dst->w = src.w;
    dst->h = src.h;
    imgsize = src.w * src.h;

    dst->dat = memalloc(BYTE, imgsize);                                     // 分配内存空间
    dst->tmp = memalloc(int, imgsize);
 
    cx = KERNEL_W / 2;
    cy = KERNEL_H / 2;
    for (x = 0; x < src.w; x++)
    {
        for (y = 0; y < src.h; y++)
        {
            int curx, cury;
            sum = 0;
            curx = x - cx;
            cury = y - cy;
            for (m = 0; m < KERNEL_W; m++)
            {
                for (n = 0; n < KERNEL_H; n++)
                {
                    sum += kernel[m][n] * (GetPixelValue(src, curx + m, cury + n) / 255.0);
                }
            }
            if (sum > 1)
            {
                sum = 1;
            }
            else if (sum < 0)
            {
                sum = 0;
            }
            BUFF2(dst->dat, x, y) = (BYTE)(sum * 0xff);
        }
    }
}
// 拉普拉斯算子变种
void ApplyLaplaceOper(const GaryImageData &src, GaryImageData *dst)
{
    const double kernel[KERNEL_W][KERNEL_H] =
    {
        0,   1,   0,    0,   1,   0,   0,
        1,   1,   0,    1,   0,   1,   1,
        0,   0,   1,    0,   1,   0,   1,
        1,   0,   0, -22.3,  0,   0,   1,
        0,   0,   1,    0,   1,   0,   1,
        1,   1,   0,    1,   0,   1,   1,
        0,   0,   1,    0,   1,   0,   0,
    };
    CreateGaryImageCov(kernel, src, dst);
}
#pragma endregion
#pragma region 字符画输出
// 这个表是按照灰度对应的字符
const char str[] =
{
    "@@&QNOBD%GmH8Abd$UwKXPZE#VShkC25eao3YnuTzxfL7vsc]|}1J?)(l=I+<>ri!*-~;:,... "
};
int wmain(int argc, wchar_t *argv[])
{
    HRESULT hr;
    ImageData img;
    GaryImageData gary, bord;
    constexpr int step = 2;                                                         // 图像转灰度的步长
    CoInitialize(NULL);
    hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID*)&pFactory);
    if (FAILED(hr))
    {
        printmsg("Can not create factory : 0x%x", hr);
        goto err;
    }
    if (argc == 1 || LoadImageData(argv[1], &img) == false)
    {
        printmsg("Can not open file");
        goto err;
    }
    CreateGaryImage(&img, &gary);                                                   // 创建灰度图像
    ApplyLaplaceOper(gary, &bord);
    /**/
    FILE *f;
    fopen_s(&f, "out.txt", "wb");
    for (int i = 0; i < gary.h; i += step)
    {
        const int maxlen = sizeof(str) - 1;
        for (int j = 0; j < gary.w; j += step)
        {
            int k = (int)(GetPixelValue(gary, j, i) / 256.0 * maxlen);
            double m = GetPixelValue(bord, j, i) / 255.0;
            k = k - 20 * (m - 0.1);
            if (k < 0)
            {
                k = 0;
            }
            fwrite(&str[k], 1, 1, f);
            fwrite(&str[k], 1, 1, f);
        }
        fwrite("\r\n", 1, 2, f);
    }
    fclose(f);

    Gary2RGBA(&bord, &img);
    SaveImageData(L"out.png", &img);
sverr:
    ReleaseImageData(&img);
    ReleaseGaryImage(&gary);
    ReleaseGaryImage(&bord);
err:
    SafeRelease(pFactory);
    CoUninitialize();
    return 0;
}
#pragma endregion

 

你可能感兴趣的:(杂七杂八)