alpha融合---PS制作带连续alpha值的png图片

最近做个项目,大致类似激萌(FaceU)那样给人头上加有趣的卡通头饰,这时候我们需要做一个头饰模板供后续程序调用。当然这个模板可以由专业设计人员设计好,但是我们没有这个条件,那么可以用激萌生成好的图片,把这个模板抠出来供我们自己的程序调用。下面介绍我们程序的核心算法

alpha融合

融合的方式有很多种,最粗暴的就是直接把前景直接覆盖到背景上面,但是这样就会像是浮在上面,很假很假。我们应该做成像透明贴纸那样以无缝“贴”在上面,所以选择以一定比例将前背景融合在一起,即alpha融合
公式:result = fg*alpha+bg*(1-alpha)
这里我们需要个核心的参数alpha,就是png图片的alpha通道,即透明度的概念,如果alpha都为1,即:result = fg 就是直接把前景覆盖在上面,很不真实,尤其是边缘部分。如果在fg边缘部分alpha从外到里连续从0变到1,那么前景就会无缝融合在背景上。
说到这里就有个关键问题了,如何得到这个alpha,当然需要借用我们强大的PS工具。

利用快速选择或魔棒或套索得到选区,单独保存成通道

为了体现细节,我们以扣头发为例
1. 先用快速选择或魔棒或套索选出主体部分,注意不要把细节选进去,只选主体。

这里写图片描述

2. 然后点上面菜单里的选择这个Tab,然后里面有个调整边缘,选中智能半径,然后按住左键有个圆圈(可以调节大小)沿边缘划,松开看到头发慢慢出现,可以反复划,直到所有头发都出来,然后确定。

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

注意这个时候,大多数会反向选择然后按delete删除,然后保存成png,进行alpha融合,但是依然会在边缘出现严重的失真
这里写图片描述

PS的delete键删除背景是如何实现的呢?其实它是将你要delete掉的地方alpha通道设为0(全图alpha通道默认为255,不透明),但是这没有达到我们前文说的要有连续的alpha,以便和背景融合时达到渐变的效果。我们刚刚的调整边缘,在图层上看到的是真实的alpha和rgb融合后的结果,所以result = alpha/255*(fg*真alpha)+bg*(1-alpha/255) ,此处的alpha是保存到png的alpha即0或255,result实际上= bg或fg*真alpha,根本没有融合的的过程,所以需要把alpha单独提取出来。

3. 存储选区

这里写图片描述
这里写图片描述
这里写图片描述
可以看到刚刚命名的alpha通道,就是我们要的alpha了,边缘的alpha值是连续变化的。

4. 分离通道单独保存这个alpha通道

这里写图片描述

点红色圆圈处,出现分离通道选项

这里写图片描述

把该通道保存下来(存储为web格式:png-24,读出来是3通道的都相同,用一个通道就好),然后后续程序处理时,读入这个alpha图作为融合的alpha。

利用蒙版一步到位

蒙版是选区的可视化表现,是将不同灰度色值转化为不同的透明度,刚刚我们把选区保存为通道其实就是蒙版。

上面那种做法,alpha通道是单独保存下来的,现在我们直接让他保存在png图片的第4个通道里

1. 前面过程和上一个方法一样就是快速选择,然后调整边缘
2.新建蒙版

这里写图片描述

这里写图片描述

然后直接存储为web格式:png-24就好,用opencv读取第4个channel就是alpha值。而且蒙版是不损坏原图的,用opencv读取前三个通道会发现还是原图。

进阶

如何我们要把脸也用蒙版也扣掉怎么做呢(同样要求alpha值连续)?
同样的先选择脸然后反选调整边缘

这里写图片描述
这里写图片描述

然后点击油漆桶,将颜色选择纯黑(纯黑表示0),注意蒙版是选中状态操作的是蒙版,不是图片。
然后发现看到边缘是渐变的。

这里写图片描述

这里写图片描述

ok,大功告成!!!

VC利用PNG图片制作异形窗口的源代码如下: 首先,在项目中添加头文件"libpng/png.h"和"zlib/zlib.h"。 然后,定义一个函数LoadPng(),用于加载PNG图片并获取图片数据和透明度信息,具体代码如下: ```c++ int LoadPng(const char* fileName, int* outWidth, int* outHeight, unsigned char** outData, std::vector<unsigned char>& outAlpha) { FILE* file = fopen(fileName, "rb"); if (!file) return 0; png_byte header[8]; fread(header, 1, 8, file); if (png_sig_cmp(header, 0, 8)) return 0; png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) return 0; png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); return 0; } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); return 0; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); fclose(file); return 0; } png_init_io(png_ptr, file); png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); int width = png_get_image_width(png_ptr, info_ptr); int height = png_get_image_height(png_ptr, info_ptr); png_byte color_type = png_get_color_type(png_ptr, info_ptr); png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); if (bit_depth == 16) png_set_strip_16(png_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png_ptr); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); png_read_update_info(png_ptr, info_ptr); png_bytep* row_pointers = new png_bytep[height]; for (int y = 0; y < height; ++y) row_pointers[y] = new png_byte[png_get_rowbytes(png_ptr, info_ptr)]; png_read_image(png_ptr, row_pointers); std::vector<unsigned char> alpha; alpha.resize(width * height); for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) { png_bytep px = &(row_pointers[y][x * 4]); alpha[y * width + x] = px[3]; px[3] = 0xFF; //将alpha设为不透明 } *outData = *row_pointers; *outWidth = width; *outHeight = height; outAlpha = alpha; for (int y = 0; y < height; ++y) delete[] row_pointers[y]; delete[] row_pointers; fclose(file); return 1; } ``` 接下来,定义如下的异形窗口类: ```c++ class ShapedWindow { public: ShapedWindow() : m_hwnd(NULL), m_hbmp(NULL), m_hit(false), m_drag(false) { LoadPng("shape.png", &m_width, &m_height, &m_data, m_alpha); } void Create(int x, int y, int width, int height) { m_hwnd = CreateWindowEx(NULL, TEXT("ShapedWindow"), NULL, WS_POPUP | WS_VISIBLE, x, y, width, height, NULL, NULL, NULL, this); } bool HitTest(int x, int y) { if (m_hit || !m_hwnd) return false; HRGN hClipRgn = CreateRectRgn(0, 0, 0, 0); GetWindowRgn(m_hwnd, hClipRgn); POINT pt = { x, y }; bool bHit = PtInRegion(hClipRgn, pt.x, pt.y) != 0; DeleteObject(hClipRgn); return bHit; } void Move(int x, int y) { SetWindowPos(m_hwnd, NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE); } private: static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ShapedWindow* pThis = NULL; if (uMsg == WM_CREATE) { LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam); pThis = reinterpret_cast<ShapedWindow*>(lpcs->lpCreateParams); SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<ULONG_PTR>(pThis)); pThis->m_hwnd = hwnd; } else { pThis = reinterpret_cast<ShapedWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA)); if (!pThis) return DefWindowProc(hwnd, uMsg, wParam, lParam); } switch (uMsg) { case WM_PAINT: { HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hwnd, &ps); if (pThis->m_hbmp != NULL) DeleteObject(pThis->m_hbmp); pThis->m_hbmp = CreateBitmap(pThis->m_width, pThis->m_height, 1, 32, pThis->m_data); HDC hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, pThis->m_hbmp); BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; AlphaBlend(hdc, 0, 0, pThis->m_width, pThis->m_height, hdcMem, 0, 0, pThis->m_width, pThis->m_height, bf); DeleteDC(hdcMem); EndPaint(hwnd, &ps); } break; case WM_NCHITTEST: if (pThis->m_hit) return HTCAPTION; break; case WM_MOUSEMOVE: if (pThis->m_drag) { int x = LOWORD(lParam); int y = HIWORD(lParam); pThis->Move(x - pThis->m_dragX, y - pThis->m_dragY); } break; case WM_LBUTTONDOWN: pThis->m_hit = pThis->HitTest(LOWORD(lParam), HIWORD(lParam)); if (pThis->m_hit) { pThis->m_drag = true; pThis->m_dragX = LOWORD(lParam); pThis->m_dragY = HIWORD(lParam); } break; case WM_LBUTTONUP: pThis->m_hit = false; pThis->m_drag = false; break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return 0; } private: HWND m_hwnd; unsigned char* m_data; int m_width, m_height; std::vector<unsigned char> m_alpha; HBITMAP m_hbmp; bool m_hit; bool m_drag; int m_dragX; int m_dragY; }; ``` 其中,LoadPng()函数用于加载PNG图片并获取图片数据和透明度信息,ShapedWindow类用于绘制异形窗口和响应窗口消息。 最后,在WinMain()函数中创建异形窗口,并进入消息循环,具体代码如下: ```c++ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc = {}; wc.cbSize = sizeof(wc); wc.lpfnWndProc = ShapedWindow::WindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = TEXT("ShapedWindow"); RegisterClassEx(&wc); ShapedWindow win; win.Create(100, 100, 300, 300); MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; } ``` 这样,一个利用PNG图片制作的异形窗口就完成了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值