news 2026/11/6 5:30:47

VC6 MFC老工程PNG透明贴图:GDI+预乘Alpha实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VC6 MFC老工程PNG透明贴图:GDI+预乘Alpha实战

简介:面向仍在 VC6.0 下维护 C++/MFC 项目的开发者,这份压缩包围绕 PNG 图片加载与透明化处理,提供一套可编译的 GDI+ 集成示例,适合需要兼容老工具链、实现图片 Alpha 通道绘制、颜色矩阵调整或窗口透明效果的读者参考。包内共 70 个文件,以 34 个 GDI+ 头文件、8 个 PNG 素材、3 个 cpp 源码及 dsp/dsw 工程文件为主,并含 lib 静态库、rc 资源、exe 与调试中间文件,压缩包约 4.73MB。已有 717 人学习下载。示例工程用 MFC 对话框时钟演示 PNG 加载、GDI+ 初始化、透明度绘制与分层窗口思路,配套头文件、库和素材较完整,便于对照理解图像绘制、颜色矩阵及资源释放流程,也可作为老版本 VC 图像处理项目的改造起点。资源也提示 VC6.0 对现代图像格式支持有限,若需更丰富的图形 API 可考虑升级工具链,但现有示例仍可用于旧项目维护、课程实验与兼容性验证。

1. 老工程里要贴 PNG,光靠 GDI 是画不出来的

VC6.0 的 MFC 工程里要贴一张带透明通道的 PNG,LoadBitmap只认 BMP,TransparentBlt只能抠一种颜色键,边上一圈锯齿怎么调都不干净——这套 VC++ PNG透明处理 的需求,几乎每个维护老项目的人都撞过。这个工程给出的解法是绕开 GDI 的位图加载,用 GDI+ 把 PNG 解成 32 位 ARGB,再转成预乘 alpha 的 DIB Section,交给 AlphaBlend 按每个像素的 alpha 混合。适合两类人:手里压着不能升级编译器的老工程、要加图标皮肤和异形界面;以及想弄清 PNG 透明在 Win32 这一层到底怎么落地的开发者。下面从环境配置一路讲到能改成自己工程里用的类。

2. 技术选型与环境落地:把 GDI+ 塞进 VC6 工程

2.1 三条路的取舍:GDI+、libpng 自己解析、纯色键透明

VC6 自带的 GDI 是个纯栅格接口,LoadImage、LoadBitmap、CreateBitmapFromResource这一套只认 BMP 和 DIB,PNG 文件里那套基于 zlib 压缩的块结构它根本不解析;CreateCompatibleBitmap出来的默认是设备相关位图,和 alpha 通道没有关系。所以「显示 PNG」这件事在老工程里从来不是调用一个 API 的问题,而是要先把带 alpha 的像素数据搬进一块 GDI 能认的内存里,再自己找机会混合。常见的三条路,我一般按「改动量 vs 最终效果」来排:

第一条是 GDI+。它自带 PNG 编解码器,Gdiplus::Bitmap::FromFile一行就能把文件读进来,LockBits拿到的就是每像素 32 位的 ARGB 数据,半透明、圆角、阴影全都能保下来。代价是工程里要多引一个 SDK 头文件和gdiplus.lib,运行时依赖 Gdiplus.dll(2000 以后的系统基本都有)。这条路的坑集中在两处:VC6 太老,头文件需要打补丁;AlphaBlend 要的是预乘 alpha,而 GDI+ 给的是非预乘,中间得自己转一手。

第二条是 libpng 加 zlib 自己解析。控制力最强,甚至可以只解 IDAT 块做流式加载,但代价是 VC6 编译第三方库要先处理一堆 C 编译器的警告和类型差异,还要自己写颜色类型转换、隔行扫描处理、CRC 校验。对于「我就想在对话框上贴个图标」的需求,这是明显的过度设计。除非项目本身要做图片编辑器或要处理超大图,我一般不走这条。

第三条是继续用色键透明,也就是TransparentBlt。它只认一种颜色当透明色,PNG 里那些 30%、50% 的羽化边缘和投影会被直接拍成实心块,边缘还会留一圈底色。它真正适合的场景是 8 位索引色图标、像素风素材,或者目标机器上连 Gdiplus.dll 都不保证存在。工程选的是第一条路加第三条路兜底:主绘制走 AlphaBlend,个别老素材走 TransparentBlt,两条路径共用一个 DIB 句柄。

2.2 VC6 里配置 GDI+ 的三处改动

VC6 的安装介质里不带 GDI+ 的开发包,得单独准备一份包含gdiplus.h、gdiplus.lib的头文件和库,放进工程目录或者加到 VC6 的搜索路径里:Tools → Options → Directories 里把 include 和 lib 两条路径补上,或者在 Project Settings → Link 页的 Object/library modules 里直接写gdiplus.lib msimg32.lib ole32.lib。前者一次配置所有工程受益,后者跟着工程走、换台机器不会丢,我一般两个都做,免得同事拉下代码后编译不过。

真正的麻烦在头文件顺序。VC6 的 Windows SDK 里没有ULONG_PTR这个类型(它是后来的平台 SDK 才补进来的),而gdiplus.h里到处在用它;同时gdiplus.h的部分内联代码依赖windows.h里windef.h定义的min/max宏。所以包含顺序不能乱:

// stdafx.h —— 顺序错了就是一堆 C2061/C2065,别随意调整 #include <windows.h> // 必须最先,gdiplus.h 依赖里面的基础类型和 min/max 宏 #include <objidl.h> // IStream,后面从内存流加载资源 PNG 要用 #ifndef ULONG_PTR_DEFINED typedef unsigned long ULONG_PTR; // VC6 没有这个类型,GDI+ 头文件里到处在用 #define ULONG_PTR_DEFINED #endif #include <gdiplus.h> // 放在 windows.h 之后,否则 min/max 找不到 // 用 pragma 链接,省得每台机器手动配 Link 页 #pragma comment(lib, "gdiplus.lib") #pragma comment(lib, "msimg32.lib") // AlphaBlend / TransparentBlt 在这里 #pragma comment(lib, "ole32.lib") // CreateStreamOnHGlobal 在这里

这里有三点值得说清楚。typedef unsigned long ULONG_PTR;在 32 位下是对的,因为指针就是 4 字节;如果哪天要编 64 位,这行必须改成条件编译,但 VC6 本身也编不了 64 位,所以工程里就按 32 位写死。#pragma comment(lib, ...)是 MSVC 特有的,比在设置页里加库名更不容易漏。最后,using namespace Gdiplus;不要写进 stdafx.h,放在具体实现的 .cpp 里就行,因为 GDI+ 的Rect、Color、Graphics这些名字和 MFC 里的一些符号容易撞,头文件里展开命名空间会连累所有包含它的编译单元。

2.3 GDI+ 生命周期:别在 WndProc 里反复 Startup

GDI+ 要求进程内先GdiplusStartup再使用任何接口,退出前GdiplusShutdown,而且这个配对必须是全局的、只做一次。我见过最典型的翻车写法是在OnPaint里 Startup、函数结束 Shutdown,短时间看不出问题,窗口一拖动就开始掉帧,因为每次 Startup 都会创建内部的后台线程和编解码器表。正确做法是把它收进一个 RAII 类,跟着CWinApp的生命周期走:

// GdiplusInit.h class CGdiplusInit { public: CGdiplusInit(); ~CGdiplusInit(); BOOL IsOk() const { return m_bOk; } private: ULONG_PTR m_nToken; // Startup 返回的令牌,Shutdown 时要原样传回去 BOOL m_bOk; }; // GdiplusInit.cpp #include "stdafx.h" #include "GdiplusInit.h" CGdiplusInit::CGdiplusInit() : m_nToken(0), m_bOk(FALSE) { Gdiplus::GdiplusStartupInput input; // 默认值:GdiplusVersion=1, DebugEventCallback=NULL, // SuppressBackgroundThread=FALSE, SuppressExternalCodecs=FALSE Gdiplus::Status st = Gdiplus::GdiplusStartup(&m_nToken, &input, NULL); m_bOk = (st == Gdiplus::Ok); } CGdiplusInit::~CGdiplusInit() { if (m_bOk && m_nToken != 0) Gdiplus::GdiplusShutdown(m_nToken); }

参数里唯一可能需要动的是SuppressBackgroundThread:设成 TRUE 之后 GDI+ 不启后台线程,图像解码变成同步的,某些需要显示进度的场景会用到,但普通的界面贴图保持默认 FALSE 就行。这个类怎么用,取决于工程是对话框还是文档视图:对话框程序一般在OnInitDialog的第一行创建成成员对象,OnDestroy时自然析构;MFC 单文档程序放进CWinApp::InitInstance的开头,配一个成员指针,在ExitInstance里 delete。

顺序上有一条不能踩的红线:所有Gdiplus::Bitmap、Graphics对象必须在GdiplusShutdown之前析构完。如果图省事把图片对象声明成全局变量或静态变量,它们的析构发生在ExitInstance之后,Shutdown 已经跑过了,退出时就会崩在某个 DLL 的清理代码里,用调试器看是一堆看不懂的调用栈,这就是典型的黑匣子故障。我一般会把图片资源挂在窗口类或 App 类上,跟着窗口销毁,退出路径清晰可见。

3. 加载链路拆解:从 PNG 文件到带预乘 alpha 的 DIB

3.1 第一步:FromFile 打开文件,顺手把格式统一成 32bppARGB

Gdiplus::Bitmap::FromFile是整条链路的入口,第二个参数是「是否使用嵌入的颜色管理信息」,一般传 FALSE,除非素材真的带了 ICC Profile 需要还原。返回值一定要判两次:指针是否为 NULL,以及GetLastStatus()是不是Ok,因为文件不存在、扩展名对但内容损坏、GDI+ 没初始化,这三种情况返回的表现不一样,只看指针会漏。

Gdiplus::Bitmap* png = Gdiplus::Bitmap::FromFile(L"skin\\btn_ok.png", FALSE); if (png == NULL || png->GetLastStatus() != Gdiplus::Ok) { delete png; // 注意:FromFile 失败时也可能返回非 NULL,必须 delete return FALSE; } UINT nW = png->GetWidth(); UINT nH = png->GetHeight(); Gdiplus::PixelFormat fmt = png->GetPixelFormat();

GetPixelFormat()拿到的值很关键。PNG 有灰度、调色板、24 位 RGB、32 位 RGBA 等好几种颜色类型,GDI+ 解出来之后格式不一定是你想要的 32bppARGB;如果后面LockBits时按 32bppARGB 去锁一张 24 位的图,LockBits会返回InvalidParameter,或者更糟——自动转成 32bpp 但其实没有 alpha 通道。稳妥的做法是先统一格式,再进后续流程:

// 不管源 PNG 是什么格式,先转成一张标准的 32bppARGB 画布 Gdiplus::Bitmap* pArgb = new Gdiplus::Bitmap(nW, nH, PixelFormat32bppARGB); { Gdiplus::Graphics g(pArgb); // SourceCopy 表示直接覆盖像素,不做任何混合,避免透明区域被叠加两次 g.SetCompositingMode(Gdiplus::CompositingModeSourceCopy); g.DrawImage(png, 0, 0, nW, nH); } delete png;

这里用CompositingModeSourceCopy而不是默认的SourceOver是有原因的:新建的画布是全透明的,用 SourceOver 绘制时,源图的半透明像素会和底色(黑)做一次混合,本来 alpha=128 的像素颜色会被压暗,最后显示出来整张图偏黑。这个现象在深色背景的素材上特别明显,很多人以为是 AlphaBlend 用错了,其实是这一步就错了。

3.2 第二步:LockBits 取原始像素,盯紧 Stride

Bitmap::LockBits把图像像素锁进一块内存,BitmapData里三个字段要记住:Scan0是起始指针,Stride是每一行的字节数,Width、Height是像素尺寸。坑在Stride:GDI+ 返回的 Stride 可能大于Width * 4,因为它按 4 字节对齐甚至更大粒度对齐,如果直接memcpy(Scan0, pDst, Width * Height * 4),图像会整体错位成一堆斜条。

Gdiplus::Rect rc(0, 0, nW, nH); Gdiplus::BitmapData bd; if (pArgb->LockBits(&rc, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bd) != Gdiplus::Ok) { return FALSE; } BYTE* pSrcBase = (BYTE*)bd.Scan0; // 按行处理,每次加 bd.Stride // 用完必须 UnlockBits,否则这块内存一直被占着,且 Bitmap 无法释放

内存布局也要心里有数。GDI+ 的PixelFormat32bppARGB在小端机器上的实际字节序是 B、G、R、A,而 Windows 32 位 DIB 的BI_RGB布局也是 B、G、R、A,两者一致,所以通道顺序不需要交换。这是整个流程里最容易被过度设计的地方,网上不少代码会写三段交换 R 和 B 的循环,其实多此一举,白白慢一倍。真正必须做的是下一步的预乘。

3.3 第三步:建 32 位 DIB Section 并做预乘

GDI 的位图分两种:设备相关位图(DDB)和设备无关位图(DIB)。DDB 是CreateCompatibleBitmap出来的,像素格式内部由驱动决定,拿不到可靠的 32 位 alpha;DIB Section 用CreateDIBSection创建,会同时返回一个像素内存指针,格式自己指定,这才是 alpha 能落地的地方。

BITMAPINFO bmi; ZeroMemory(&bmi, sizeof(BITMAPINFO)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = nW; bmi.bmiHeader.biHeight = -(LONG)nH; // 负高度 = 自上而下,行序和 GDI+ 一致 bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; void* pBits = NULL; // 第一个参数传 NULL 在 BI_RGB 下是合法的,不需要屏幕 DC HBITMAP hDib = ::CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0); if (hDib == NULL || pBits == NULL) { pArgb->UnlockBits(&bd); return FALSE; }

biHeight写成负数是刻意为之:GDI+ 的Scan0指向第一行像素(自上而下),而正高度 DIB 的第一行在图像底部(自下而上),写成负数能省掉一次整图翻转,行序直接对齐。这个细节不写也不会报错,但你会看到图像上下颠倒。

接下来是预乘。预乘 alpha 的意思是每个像素的 R、G、B 已经乘过自己的 alpha 值,范围从 0 到 alpha。AlphaBlend 使用AC_SRC_ALPHA时要求源位图就是这种格式,它不再做乘法,直接做加法和移位;如果传入的是非预乘数据,相当于把颜色当成已经乘过的结果,半透明区域的颜色会被整体放大,表现出来就是圆角外的阴影发白发亮。

for (UINT y = 0; y < nH; ++y) { const BYTE* pSrc = pSrcBase + y * bd.Stride; // 源:按 Stride 走 BYTE* pDst = (BYTE*)pBits + y * nW * 4; // 目标:DIB 行宽就是 nW*4 for (UINT x = 0; x < nW; ++x) { BYTE b = pSrc[0]; BYTE g = pSrc[1]; BYTE r = pSrc[2]; BYTE a = pSrc[3]; // +127 是四舍五入,纯粹用 /255 会让半透明区域整体偏暗一点点 pDst[0] = (BYTE)((b * a + 127) / 255); pDst[1] = (BYTE)((g * a + 127) / 255); pDst[2] = (BYTE)((r * a + 127) / 255); pDst[3] = a; // alpha 本身保持不变 pSrc += 4; pDst += 4; } } pArgb->UnlockBits(&bd);

这段循环是整个透明效果的地基。a为 0 时三个颜色分量都归零,AlphaBlend 加目标色后画面不变,完全透明;a为 255 时颜色原样保留,完全不透明;中间值就按比例混合。除法用/255而不是位移,是因为 255 不是 2 的幂,>>8会让结果偏小,白色区域会发灰。大图用这个循环会有点慢(一张 1024×1024 大约百万次迭代,VC6 不开优化也能在几十毫秒内跑完),所以只在加载时做一次,绘制时直接复用句柄,不要每帧重算。

3.4 资源内嵌 PNG 的加载:CreateStreamOnHGlobal 走内存流

把 PNG 当外部文件放在 exe 旁边,部署时最容易丢。工程里更常见的做法是把 PNG 编译进资源,用自定义资源类型(比如PNG)而不是BITMAP,因为后者会被 RC 编译器尝试解析,PNG 的二进制内容会被改坏。资源加载后要转成IStream才能喂给Bitmap::FromStream,这一步最省事的写法是用CreateStreamOnHGlobal:

HRSRC hRes = ::FindResource(hInst, MAKEINTRESOURCE(IDR_PNG_LOGO), _T("PNG")); if (hRes == NULL) return FALSE; DWORD dwSize = ::SizeofResource(hInst, hRes); HGLOBAL hData = ::LoadResource(hInst, hRes); LPVOID pData = ::LockResource(hData); // 必须拷贝一份到可移动内存,资源内存是只读的,IStream 需要可写句柄 HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, dwSize); if (hMem == NULL) return FALSE; void* pDst = ::GlobalLock(hMem); memcpy(pDst, pData, dwSize); ::GlobalUnlock(hMem); IStream* pStream = NULL; // 第二个参数 TRUE 表示流释放时自动 GlobalFree 这块内存 if (FAILED(::CreateStreamOnHGlobal(hMem, TRUE, &pStream))) { ::GlobalFree(hMem); return FALSE; } Gdiplus::Bitmap* pBmp = Gdiplus::Bitmap::FromStream(pStream, FALSE); pStream->Release(); // 流可以被释放,Bitmap 内部已经拷贝了解码后的数据

一个常被误解的点:CreateStreamOnHGlobal不需要先CoInitialize,它是 ole32 里少数不依赖 COM 初始化的函数,所以对话框程序直接调用没问题。但Bitmap::FromStream依赖 GDI+ 已经 Startup,顺序不能反。另外注意LoadResource之后没有对应的FreeResource——在 Win32 下资源是映射进进程空间的,不需要释放,写了反而是错的(16 位时代才需要)。

4. 透明绘制:AlphaBlend、TransparentBlt 与异形窗口

4.1 AlphaBlend 的参数表与调用姿势

到这一步,手上已经有一个 32 位预乘的 DIB 句柄,绘制就只剩调用AlphaBlend一件事。这个函数的参数多,但真正会传错的只有最后那个BLENDFUNCTION:

参数含义常用取值
hdcDest目标 DC窗口或双缓冲内存 DC
xoriginDest / yoriginDest目标左上角坐标按布局算
wDest / hDest目标宽高等于源尺寸时不缩放
hdcSrc源 DC,必须已 SelectObject 那张 DIB独立的兼容 DC
xoriginSrc / yoriginSrc源图起始坐标通常是 0, 0
wSrc / hSrc源图区域宽高图像原始尺寸
ftn.BlendOp混合方式AC_SRC_OVER
ftn.BlendFlags保留字段必须为 0
ftn.SourceConstantAlpha整体不透明度 0-255255;做淡入淡出时改这里
ftn.AlphaFormat来源 alpha 方式AC_SRC_ALPHA(每像素),0(不用)

调用姿势上,最容易漏的是SelectObject和还原。把 DIB 选进一个兼容 DC 之后,函数结束要选回原来的对象,否则这个 DC 一直占着那张位图,后面想重建或释放都会失败:

void DrawPngAlpha(HDC hdcDst, int x, int y, int w, int h, HBITMAP hDib, int nSrcW, int nSrcH) { HDC hdcSrc = ::CreateCompatibleDC(hdcDst); HGDIOBJ hOld = ::SelectObject(hdcSrc, hDib); BLENDFUNCTION bf; bf.BlendOp = AC_SRC_OVER; bf.BlendFlags = 0; bf.SourceConstantAlpha = 255; bf.AlphaFormat = AC_SRC_ALPHA; // 源位图必须是预乘过的 32bpp // 缩放时用 HALFTONE,否则放大后的边缘是硬锯齿 ::SetStretchBltMode(hdcDst, HALFTONE); ::SetBrushOrgEx(hdcDst, 0, 0, NULL); ::AlphaBlend(hdcDst, x, y, w, h, hdcSrc, 0, 0, nSrcW, nSrcH, bf); ::SelectObject(hdcSrc, hOld); ::DeleteDC(hdcSrc); }

SourceConstantAlpha这个参数值得单独说:它和每像素 alpha 是相乘关系,最终 alpha = 像素 alpha × 常量 / 255。所以做按钮的禁用态、弹出窗口淡入、鼠标悬停高亮,都不用另外准备一套图,改这个值就行。工程里做整体淡入时就是拿一个定时器把它从 0 加到 255,每帧重绘一次。

4.2 TransparentBlt 的适用边界

TransparentBlt在msimg32.lib里,接口形态和StretchBlt几乎一样,多一个颜色键参数:

// 最后一个参数是"被视为完全透明"的颜色键,这里用常见的洋红 ::TransparentBlt(hdcDst, x, y, w, h, hdcSrc, 0, 0, nSrcW, nSrcH, RGB(255, 0, 255));

它的能力边界必须说清楚:只有「完全透明」和「完全不透明」两档,没有中间值。PNG 里那些 alpha 在 1 到 254 之间的羽化像素,要么被当成不透明画出来(边缘留下脏色),要么被当成透明抠掉(边缘变成硬切)。所以它只适合两类素材:8 位索引色的老图标,以及本来就是硬边缘的像素风图形。工程里保留它是因为有些老素材是别的系统导出的,边缘做了颜色键处理,用 AlphaBlend 反而会把色键颜色画出来。

4.3 双缓冲:把闪烁压下去

只要贴图,就一定会碰到闪烁。原因不是 AlphaBlend 慢,而是默认的 WM_ERASEBKGND 会用背景刷把客户区刷成一片底色,然后 WM_PAINT 再贴一次,中间这段时间用户能看到。解决办法是两步:第一步在OnEraseBkgnd里直接返回 TRUE,声明背景由自己负责;第二步在OnPaint里先把所有内容画到内存 DC,最后一次性BitBlt到屏幕。

BOOL CMyView::OnEraseBkgnd(CDC* pDC) { return TRUE; // 背景已经在 OnPaint 的双缓冲里画过了,这里不要再擦 } void CMyView::OnPaint() { CPaintDC dc(this); CRect rc; GetClientRect(&rc); CDC memDC; memDC.CreateCompatibleDC(&dc); CBitmap bmpBuf; bmpBuf.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height()); CBitmap* pOldBmp = memDC.SelectObject(&bmpBuf); // 1. 先铺背景 memDC.FillSolidRect(&rc, RGB(240, 240, 240)); // 2. 再贴 PNG DrawPngAlpha(memDC.GetSafeHdc(), 20, 20, m_nPngW, m_nPngH, m_hPngDib, m_nPngW, m_nPngH); // 3. 一次性贴回屏幕 dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBmp); }

注意双缓冲里的CreateCompatibleBitmap(&dc, ...)建的是 DDB,它没有 alpha 通道,但没关系——它只是背景

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 5:28:31

LAS点云Classification到PointNet++语义分割全链路

简介&#xff1a;面向希望在自有点云数据上复现PointNet与PointNet语义分割流程的开发者与研究者&#xff0c;这组材料围绕带Classification属性的LAS点云&#xff0c;提供了可直接用于训练、验证和预测的样例数据&#xff0c;并保留两套网络实现&#xff0c;方便在同一批数据上…

作者头像 李华
网站建设 2026/11/6 5:27:35

VC老工程复活:坦克RPG源码的Win32拆解与二次开发

简介&#xff1a;这是一份基于VC8.0与DirectX9.0 SDK开发的重装机兵&#xff08;MetalMax&#xff09;复刻游戏及配套源码包&#xff0c;面向想研究2D角色扮演游戏实现、学习C与DirectX项目组织的开发者及游戏爱好者。压缩包共545个文件&#xff0c;约6.97MB&#xff0c;以png纹…

作者头像 李华
网站建设 2026/11/6 5:27:19

开源7B/14B模型反超同规模SOTA:数据清洗、分词器与对齐流水线是关键

简介&#xff1a;面向大模型开发者、人工智能研究者与进阶学习者的技术资料&#xff0c;以单份PDF收录通义千问Qwen1官方技术报告&#xff0c;重点解决开源大语言模型技术路线不清、论文要点难梳理的问题&#xff0c;适合论文研读、课程汇报与应用选型参考。压缩包共1个文件&am…

作者头像 李华
网站建设 2026/11/6 5:23:02

工程单和零售单抢同一批货,建材ERP的库存锁定该做到什么粒度?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/6 5:19:43

奥特曼预告下一代GPT,1万个Agent攻下千禧难题

奥特曼透露下一代GPT进展 下一代 GPT 的一个重要线索&#xff0c;Sam Altman 透露了。 当地时间 11 月 2 日&#xff0c;在阿布扎比举行的 ADIPEC 2026 能源大会上&#xff0c;Altman 谈到 OpenAI 此前公布的纳维–斯托克斯数学突破时透露&#xff0c;完成这项工作的&#xf…

作者头像 李华