使用libjpeg区域显示超大图

很久以前遇到的一个问题,当时使用GDI+绘图,打开超大jpeg的时候,会卡很久直到图片完全解码完毕才能一次性显示出来。

当时设想的完美解决方案是逐步解码,逐步显示,但是技术水平所限,一直找不到解决方案,最后是使用了等待进度条使用户体验稍好一点,并没有真正解决。

现在GDI+已经可以在MFC里直接使用CImage了,但是依然没有区域解码的接口。网络上也搜索了,可能是搜索方式不对,没有找到有价值的东西,于是想通过学习libjpeg开源库来找出解决方案。

稍微学习了libJpeg之后发现其实很简单。下面直接上结果。jpeg图像好像分两种,一种线性的一种渐进的,并没有深入了解,这个方法应该是适用于线性的。

1.新建一个线程,用于载入和解码大图。因为大图解码需要不少时间,如果在UI线程的话,会导致界面卡死一段时间,所以要放到新线程中。

DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
	//文件路径通过线程参数传入,可自行修改使用自己习惯的方式实现。
	LPCTSTR lpszImage = (LPCTSTR)lpParam;

	//以下基本都是libJpeg的例子里的,自己的实现都加了注释
	struct jpeg_decompress_struct cinfo;
	struct jpeg_error_mgr jerr;
	cinfo.err = jpeg_std_error(&jerr);
	jpeg_create_decompress(&cinfo);

	FILE *f;
	errno_t err = _tfopen_s(&f, lpszImage, _T("rb"));
	if (f == NULL)
	{
		MessageBox(0, _T("Open file error!"), _T("error!") , MB_OK);
		return 0;
	}
	jpeg_stdio_src(&cinfo, f);

	jpeg_read_header(&cinfo, TRUE);
	//上面读取了文件头,获取了图像信息,填充到下面的BITMAPINFO bi结构体中
	//bi是全局变量,因为线程里要填充,后面绘制的地方要用到。可自行使用习惯的方式实现
	bi.bmiHeader.biSize = sizeof(BITMAPINFO);
	bi.bmiHeader.biWidth = cinfo.image_width;
	bi.bmiHeader.biHeight = cinfo.image_height;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 24;
	bi.bmiHeader.biCompression = BI_RGB;
	//由于要内存绘制所以要四字节对齐
	int nStride = ALIGN(cinfo.image_width*cinfo.num_components , 4);
	//申请解码空间,同时也是绘制的时候的图像数据
	pBuff = new BYTE[nStride * cinfo.image_height];

	jpeg_start_decompress(&cinfo);

	JSAMPROW row_pointer[1];
	DWORD dwTick = GetTickCount();
	while (cinfo.output_scanline < cinfo.output_height)
	{
		//按行获取解码数据要存放的地址,传递给jpeg_read_scanlines第二个参数
		row_pointer[0] = &pBuff[(cinfo.output_height - cinfo.output_scanline - 1) * nStride];
		jpeg_read_scanlines(&cinfo, row_pointer,1);
		DWORD dwCurrentTick = GetTickCount();
		if (dwCurrentTick - dwTick > 500)
		{
			//请求界面刷新。加入dwTick是怕刷新太快闪屏
			//后来使用了双缓冲绘制,不清楚去掉会不会闪
			theApp.GetMainWnd()->Invalidate(FALSE);
			dwTick = dwCurrentTick;
		}
	}
	jpeg_finish_decompress(&cinfo);

	jpeg_destroy_decompress(&cinfo);
	fclose(f);

	return 0;
}
线程代码涉及到两个全局变量:

BYTE *pBuff = 0;
BITMAPINFO bi = { 0 };

2.我使用的是MFC对话框,绘制区域是对话框中的一个Picture Control,定义了一个CStatic控件m_ctrl_Frame,绘制代码在OnPaint中。

if (pBuff)
{
	CRect rect;
	m_ctrl_Frame.GetWindowRect(&rect);
	this->ScreenToClient(&rect);
	rect.OffsetRect(-rect.left, -rect.top);


	HDC hDC = m_ctrl_Frame.GetDC()->GetSafeHdc();
	HDC hDCMem;
	HBITMAP hBmpMem, hPreBmp;
	hDCMem = CreateCompatibleDC(hDC);
	// 创建一块指定大小的位图  
	hBmpMem = CreateCompatibleBitmap(hDC, rect.right, rect.bottom);
	// 将该位图选入到内存DC中,默认是全黑色的  
	hPreBmp = (HBITMAP)SelectObject(hDCMem, hBmpMem);


	/* 在双缓冲中绘图 */
	// 加载背景位图  
	SetStretchBltMode(hDCMem, STRETCH_HALFTONE);
	int nRet = StretchDIBits(hDCMem, 0, 0, rect.Width(), rect.Height(),
		0, 0, bi.bmiHeader.biWidth, bi.bmiHeader.biHeight , 
		pBuff, &bi, DIB_RGB_COLORS, SRCCOPY);


	/* 将双缓冲区图像复制到显示缓冲区 */
	BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY);


	/* 释放资源 */
	SelectObject(hDCMem, hPreBmp);
	DeleteObject(hBmpMem);
	DeleteDC(hDCMem);
}


绘制代码也很简单,就是网上搜索到的双缓冲略作修改。感谢作者。

测试了一下速度很快,了结一个心愿。感谢libJpeg作者。

***补充:按照这样的方法解码之后RGB颜色是BGR排列的,需要修改一下libJpeg库的“jmorecfg.h”文件,重新编译libJpeg库再进行链接。

jmorecfg.h的400行左右有定义:

#define RGB_RED		0	/* Offset of Red in an RGB scanline element */
#define RGB_GREEN	1	/* Offset of Green */
#define RGB_BLUE	2	/* Offset of Blue */

改成:

#define RGB_RED		2	/* Offset of Red in an RGB scanline element */
#define RGB_GREEN	1	/* Offset of Green */
#define RGB_BLUE	0	/* Offset of Blue */


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yes2/article/details/70941216
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭