赞
踩
这个是我顺手写的玩具,因此有好些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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。