当前位置:   article > 正文

C++实现图像转字符画_c++字符画

c++字符画

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

鲜红的幼月

效果可以吧。

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

  1. /*
  2. | Img2Char
  3. | 文件名称: img2chr.cpp
  4. | 文件作用: 唯一的源文件
  5. | 创建日期: 2020-04-16
  6. | 更新日期: 2020-04-18
  7. | 开发人员: JuYan
  8. +----------------------------
  9. Copyright (C) JuYan, all rights reserved.
  10. 该程序可以把你的图像变成字符画
  11. WARNING: bug有好几个
  12. */
  13. #pragma region includedefine
  14. #include <list>
  15. #include <math.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <stdarg.h>
  19. #include <assert.h>
  20. #include <Windows.h>
  21. #include <algorithm>
  22. #include <wincodec.h>
  23. #pragma comment(lib, "Windowscodecs.lib")
  24. #define memalloc(type, num) (type*)_memalloc(sizeof(type) * (num))
  25. #define BUFF2(p, i, j) (*((p) + (j) * w + (i)))
  26. #define KERNEL_W 7
  27. #define KERNEL_H 7
  28. #pragma endregion
  29. #pragma region struct定义
  30. enum ImageChannle
  31. {
  32. Channel_B, Channel_G, Channel_R, Channel_A
  33. };
  34. typedef struct tagImageData
  35. {
  36. BYTE *rgba;
  37. int w, h;
  38. } ImageData;
  39. typedef struct tagGaryImageData
  40. {
  41. BYTE *dat; // 0xff是顶级
  42. int *tmp; // 在各个处理过程中可能用到的temp数据
  43. int w, h;
  44. } GaryImageData;
  45. #pragma endregion
  46. #pragma region 全局变量
  47. IWICImagingFactory *pFactory = NULL;
  48. #pragma endregion
  49. #pragma region 杂项函数
  50. // 打印错误信息
  51. void printmsg(const char *msg, ...)
  52. {
  53. va_list va;
  54. va_start(va, msg);
  55. vfprintf(stderr, msg, va);
  56. putchar('\n');
  57. va_end(va);
  58. }
  59. // 分配内存和释放内存
  60. void* _memalloc(size_t sz)
  61. {
  62. void *p;
  63. p = malloc(sz);
  64. if (p == NULL)
  65. {
  66. printmsg("Can not allocate %d bytes on heap.", sz);
  67. assert(0);
  68. abort();
  69. }
  70. return p;
  71. }
  72. void memfree(void *p)
  73. {
  74. if (p != NULL)
  75. {
  76. free(p);
  77. }
  78. p = NULL;
  79. }
  80. #pragma endregion
  81. #pragma region 图片读写
  82. // 释放一个接口
  83. template<class T> inline void SafeRelease(T * &p)
  84. {
  85. if (p)
  86. {
  87. p->Release();
  88. }
  89. p = NULL;
  90. }
  91. // 读取图片, 得到rgba, 失败返回false
  92. bool LoadImageData(const wchar_t *file, ImageData *res)
  93. {
  94. bool ret;
  95. BYTE *dat;
  96. HRESULT hr;
  97. WICRect rcLock;
  98. UINT i, j, k, datSz, dataStride, imgW, imgH;
  99. int dataStep;
  100. BYTE *pImgData;
  101. IWICBitmap *pBitmap = NULL;
  102. IWICBitmapLock *pLock = NULL;
  103. WICPixelFormatGUID formatGUID;
  104. IWICBitmapDecoder *pDecoder = NULL; // 解码器要即时创建和释放
  105. IWICBitmapFrameDecode *pDecFrame = NULL;
  106. ret = true;
  107. res->w = 0;
  108. res->h = 0;
  109. res->rgba = NULL;
  110. hr = pFactory->CreateDecoderFromFilename(file, NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &pDecoder);
  111. if (FAILED(hr))
  112. {
  113. printmsg("Can not load image : 0x%x", hr);
  114. return false;
  115. }
  116. hr = pDecoder->GetFrame(0, &pDecFrame);
  117. if (FAILED(hr))
  118. {
  119. ret = false;
  120. goto err;
  121. }
  122. // 载入图像信息
  123. pDecFrame->GetSize(&imgW, &imgH);
  124. pDecFrame->GetPixelFormat(&formatGUID); // 取得图像编码
  125. if (IsEqualGUID(formatGUID, GUID_WICPixelFormat32bppBGRA))
  126. {
  127. dataStep = 4;
  128. }
  129. else if (IsEqualGUID(formatGUID, GUID_WICPixelFormat24bppBGR))
  130. {
  131. dataStep = 3;
  132. }
  133. else {
  134. ret = false;
  135. printmsg("Unknow image format");
  136. goto err;
  137. }
  138. // 拷贝图像数据
  139. rcLock.X = 0;
  140. rcLock.Y = 0;
  141. rcLock.Width = imgW;
  142. rcLock.Height = imgH; // 接下来我们创建位图以得到数据
  143. hr = pFactory->CreateBitmapFromSource(pDecFrame, WICBitmapCacheOnDemand, &pBitmap);
  144. if (FAILED(hr))
  145. {
  146. ret = false;
  147. goto err;
  148. }
  149. pBitmap->Lock(&rcLock, WICBitmapLockWrite, &pLock);
  150. pLock->GetStride(&dataStride); // 取得一行有多少字节
  151. hr = pLock->GetDataPointer(&datSz, &dat); // 取得图像数据
  152. if (FAILED(hr))
  153. {
  154. ret = false;
  155. printmsg("Can not get image data from memory.");
  156. goto err;
  157. }
  158. pImgData = memalloc(BYTE, imgW * imgH * sizeof(UINT)); // 最后, 拷贝像素数据
  159. for (i = 0, j = 0; i < dataStride * imgH; i += dataStride)
  160. {
  161. for (k = 0; k < imgW * dataStep; k += dataStep, j += 4)
  162. {
  163. pImgData[j + 0] = dat[i + k + 0];
  164. pImgData[j + 1] = dat[i + k + 1];
  165. pImgData[j + 2] = dat[i + k + 2];
  166. if (dataStep == 4)
  167. {
  168. pImgData[j + 3] = dat[i + k + 3];
  169. }
  170. else {
  171. pImgData[j + 3] = 0xff;
  172. }
  173. }
  174. }
  175. res->w = (int)imgW;
  176. res->h = (int)imgH;
  177. res->rgba = pImgData;
  178. err:
  179. SafeRelease(pLock);
  180. SafeRelease(pBitmap);
  181. SafeRelease(pDecFrame);
  182. SafeRelease(pDecoder);
  183. return ret;
  184. }
  185. // 保存一段数据, 格式为32位的PNG
  186. bool SaveImageData(const wchar_t *file, const ImageData *dat)
  187. {
  188. bool ret;
  189. UINT w, h;
  190. HRESULT hr;
  191. IWICStream *pStream = NULL;
  192. IWICBitmapEncoder *pEncoder = NULL;
  193. IWICBitmapFrameEncode *pBitmapFrame = NULL;
  194. WICPixelFormatGUID formatGUID = GUID_WICPixelFormat32bppBGRA;
  195. ret = true;
  196. w = (UINT)dat->w;
  197. h = (UINT)dat->h;
  198. pFactory->CreateStream(&pStream);
  199. hr = pStream->InitializeFromFilename(file, GENERIC_WRITE);
  200. if (FAILED(hr))
  201. {
  202. ret = false;
  203. printmsg("Can not save file");
  204. goto err;
  205. }
  206. hr = pFactory->CreateEncoder(GUID_ContainerFormatPng, NULL, &pEncoder); // png编码器
  207. if (FAILED(hr))
  208. {
  209. ret = false;
  210. printmsg("Can not create encoder");
  211. goto err;
  212. }
  213. pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
  214. hr = pEncoder->CreateNewFrame(&pBitmapFrame, NULL); // 创建一个帧
  215. if (FAILED(hr))
  216. {
  217. ret = false;
  218. goto err;
  219. }
  220. pBitmapFrame->Initialize(NULL);
  221. pBitmapFrame->SetSize(w, h);
  222. pBitmapFrame->SetPixelFormat(&formatGUID);
  223. hr = pBitmapFrame->WritePixels(
  224. h,
  225. w * sizeof(UINT),
  226. w * h * sizeof(UINT),
  227. dat->rgba
  228. );
  229. if (FAILED(hr))
  230. {
  231. ret = false;
  232. goto err;
  233. }
  234. pBitmapFrame->Commit(); // 提交更改
  235. pEncoder->Commit();
  236. pStream->Commit(STGC_DEFAULT);
  237. err:
  238. SafeRelease(pStream);
  239. SafeRelease(pBitmapFrame);
  240. SafeRelease(pEncoder);
  241. return ret;
  242. }
  243. // 释放ImageData
  244. void ReleaseImageData(ImageData *p)
  245. {
  246. memfree(p->rgba);
  247. }
  248. #pragma endregion
  249. #pragma region 灰度处理
  250. void CreateGaryImage(const ImageData *src, GaryImageData *res)
  251. {
  252. BYTE *pres;
  253. int imgsize, r;
  254. const BYTE *psrc;
  255. assert(src->rgba);
  256. res->w = src->w;
  257. res->h = src->h;
  258. imgsize = res->w * res->h;
  259. res->dat = memalloc(BYTE, imgsize); // 分配内存空间
  260. res->tmp = memalloc(int, imgsize);
  261. pres = res->dat;
  262. psrc = src->rgba;
  263. while (imgsize-- > 0)
  264. {
  265. if (psrc[Channel_A] != 0)
  266. {
  267. r = (int)(psrc[Channel_R] * 0.3 + psrc[Channel_G] * 0.59 + psrc[Channel_B] * 0.11);
  268. }
  269. else {
  270. r = 0xff; // 完全透明的地方设置为白色
  271. }
  272. *pres++ = r > 0xff ? 0xff : r;
  273. psrc += 4; // R G B A 四个通道
  274. }
  275. }
  276. // 把灰度图转到一个已有的RGBA数据上
  277. void Gary2RGBA(const GaryImageData *src, ImageData *dst)
  278. {
  279. int imgsize;
  280. BYTE * pdst;
  281. const BYTE *psrc;
  282. assert(src->dat);
  283. pdst = dst->rgba;
  284. psrc = src->dat;
  285. imgsize = src->w * src->h;
  286. while (imgsize-- > 0) // 这玩意说白了就是把rgb三个通道设置为灰度值
  287. {
  288. pdst[Channel_A] = 0xff;
  289. pdst[Channel_R] = *psrc;
  290. pdst[Channel_G] = *psrc;
  291. pdst[Channel_B] = *psrc;
  292. pdst += 4;
  293. psrc++;
  294. }
  295. }
  296. // 释放灰度图
  297. void ReleaseGaryImage(GaryImageData *p)
  298. {
  299. if (p->dat != NULL)
  300. {
  301. memfree(p->tmp);
  302. memfree(p->dat);
  303. }
  304. }
  305. #pragma endregion
  306. #pragma region 拉普拉斯
  307. template<typename T> int GetPixelValue(T *dat, int x, int y, int maxw, int maxh)
  308. {
  309. if (x < 0)
  310. x = 0;
  311. if (y < 0)
  312. y = 0;
  313. if (x >= maxw)
  314. x = maxw - 1;
  315. if (y >= maxh)
  316. y = maxh - 1;
  317. return (int)*(dat + y * maxw + x);
  318. }
  319. inline int GetPixelValue(const GaryImageData &img, int x, int y)
  320. {
  321. return GetPixelValue(img.dat, x, y, img.w, img.h);
  322. }
  323. // 对灰度图像进行卷积
  324. void CreateGaryImageCov(const double kernel[KERNEL_W][KERNEL_H], const GaryImageData &src, GaryImageData *dst)
  325. {
  326. int x, y;
  327. int m, n, w;
  328. int imgsize, cx, cy;
  329. double sum;
  330. w = src.w;
  331. dst->w = src.w;
  332. dst->h = src.h;
  333. imgsize = src.w * src.h;
  334. dst->dat = memalloc(BYTE, imgsize); // 分配内存空间
  335. dst->tmp = memalloc(int, imgsize);
  336. cx = KERNEL_W / 2;
  337. cy = KERNEL_H / 2;
  338. for (x = 0; x < src.w; x++)
  339. {
  340. for (y = 0; y < src.h; y++)
  341. {
  342. int curx, cury;
  343. sum = 0;
  344. curx = x - cx;
  345. cury = y - cy;
  346. for (m = 0; m < KERNEL_W; m++)
  347. {
  348. for (n = 0; n < KERNEL_H; n++)
  349. {
  350. sum += kernel[m][n] * (GetPixelValue(src, curx + m, cury + n) / 255.0);
  351. }
  352. }
  353. if (sum > 1)
  354. {
  355. sum = 1;
  356. }
  357. else if (sum < 0)
  358. {
  359. sum = 0;
  360. }
  361. BUFF2(dst->dat, x, y) = (BYTE)(sum * 0xff);
  362. }
  363. }
  364. }
  365. // 拉普拉斯算子变种
  366. void ApplyLaplaceOper(const GaryImageData &src, GaryImageData *dst)
  367. {
  368. const double kernel[KERNEL_W][KERNEL_H] =
  369. {
  370. 0, 1, 0, 0, 1, 0, 0,
  371. 1, 1, 0, 1, 0, 1, 1,
  372. 0, 0, 1, 0, 1, 0, 1,
  373. 1, 0, 0, -22.3, 0, 0, 1,
  374. 0, 0, 1, 0, 1, 0, 1,
  375. 1, 1, 0, 1, 0, 1, 1,
  376. 0, 0, 1, 0, 1, 0, 0,
  377. };
  378. CreateGaryImageCov(kernel, src, dst);
  379. }
  380. #pragma endregion
  381. #pragma region 字符画输出
  382. // 这个表是按照灰度对应的字符
  383. const char str[] =
  384. {
  385. "@@&QNOBD%GmH8Abd$UwKXPZE#VShkC25eao3YnuTzxfL7vsc]|}1J?)(l=I+<>ri!*-~;:,... "
  386. };
  387. int wmain(int argc, wchar_t *argv[])
  388. {
  389. HRESULT hr;
  390. ImageData img;
  391. GaryImageData gary, bord;
  392. constexpr int step = 2; // 图像转灰度的步长
  393. CoInitialize(NULL);
  394. hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID*)&pFactory);
  395. if (FAILED(hr))
  396. {
  397. printmsg("Can not create factory : 0x%x", hr);
  398. goto err;
  399. }
  400. if (argc == 1 || LoadImageData(argv[1], &img) == false)
  401. {
  402. printmsg("Can not open file");
  403. goto err;
  404. }
  405. CreateGaryImage(&img, &gary); // 创建灰度图像
  406. ApplyLaplaceOper(gary, &bord);
  407. /**/
  408. FILE *f;
  409. fopen_s(&f, "out.txt", "wb");
  410. for (int i = 0; i < gary.h; i += step)
  411. {
  412. const int maxlen = sizeof(str) - 1;
  413. for (int j = 0; j < gary.w; j += step)
  414. {
  415. int k = (int)(GetPixelValue(gary, j, i) / 256.0 * maxlen);
  416. double m = GetPixelValue(bord, j, i) / 255.0;
  417. k = k - 20 * (m - 0.1);
  418. if (k < 0)
  419. {
  420. k = 0;
  421. }
  422. fwrite(&str[k], 1, 1, f);
  423. fwrite(&str[k], 1, 1, f);
  424. }
  425. fwrite("\r\n", 1, 2, f);
  426. }
  427. fclose(f);
  428. Gary2RGBA(&bord, &img);
  429. SaveImageData(L"out.png", &img);
  430. sverr:
  431. ReleaseImageData(&img);
  432. ReleaseGaryImage(&gary);
  433. ReleaseGaryImage(&bord);
  434. err:
  435. SafeRelease(pFactory);
  436. CoUninitialize();
  437. return 0;
  438. }
  439. #pragma endregion

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/94295
推荐阅读
相关标签
  

闽ICP备14008679号