C++实现图像转字符画

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

鲜红的幼月

效果可以吧。

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

/*
 | Img2Char
 | 文件名称: img2chr.cpp
 | 文件作用: 唯一的源文件
 | 创建日期: 2020-04-16
 | 更新日期: 2020-04-18
 | 开发人员: JuYan
 +----------------------------
 Copyright (C) JuYan, all rights reserved.
 该程序可以把你的图像变成字符画
 WARNING: bug有好几个
*/
#pragma region include和define
#include <list>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <Windows.h>
#include <algorithm>
#include <wincodec.h>
#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<class T> 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<typename T> 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

 

  • 14
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
将图片换为字符是一种将图片中的像素点映射为对应的字符的过程。通过使用不同的字符代替不同灰度级别的像素,可以在文本环境中显示出图像的近似效果。这种技术可以通过许多方式实现,包括使用程序编写的算法和在线字符生成器。 生成字符的过程通常包括以下几个步骤:首先,将原始图片换为灰度图像,即使得每个像素点只有一个灰度值(黑白照片)。其次,将灰度图像中的像素值映射到一组字符中,例如使用ASCII码中的字符集。不同的灰度级别对应不同的字符。然后,将映射后的字符按照相应的排列方式,例如每行固定字符数或根据像素灰度级别进行排列,重新组织为文本文件。最后,将生成的文本文件输出显示,以呈现出与原始图像相似的字符效果。 生成字符的过程可以根据不同的需求和算法进行调整和优化。一些算法可以在换过程中添加一些光影效果,以产生更真实的效果。此外,还可以通过改变字符的颜色或使用更多的字符集来增强字符的细节和质量。 使用字符的好处是可以在某些情况下以较小的文件大小传输和存储图像,也可以在文本环境中展示图像字符也有一定的美学价值和情怀意义,成为一种独特的艺术形式。 总之,将图片换为字符是一种将图像换为文本形式的过程,可以通过一系列算法和字符映射方式来实现。这种技术可以在文本环境中显示图像的近似效果,有一定的实用性和美学价值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值