• 扫描电子书页面存储的问题及解决方案(1):页边空与GIF文件格式


    作者:马健
    邮箱:stronghorse_mj@hotmail.com
    主页:https://www.cnblogs.com/stronghorse/

    一、问题的提出

    扫描电子书形成的图像,与常规风光人物照片、广告图片等有所不同,因此一般需要区别对待。例如,出于美观等因素考虑,常规书籍都会在页面四周留出页边空(或称“页边距”),如下图所示。页边空区域内其实并无有效内容,但没有又不行——阅读尤其是手持阅读的时候其实很多人是希望能够裁减掉页边空的,但一本扫描电子书如果一上来就根本没有页边空,肯定会被下载到的人骂。

    通常在对这种书籍扫描图像进行存储的时候,都把页边空当做图像的有效部分进行编码,但在编码算法不变的情况下,这样编码出来的文件长度,显然要比排除页边空部分,仅针对版心部分进行编码的文件长度要长,毕竟古话说得好:苦干不如巧干,巧干不如少干,少干不如不干。

    所以有必要寻找一种解决方案,能在不影响实际页面显示效果的情况下,又不为没啥实际内容的页边空部分耗费宝贵的编码空间,即只存储排除页边空(裁边)后的版心内容。

    二、在GIF文件格式中排除页边空

    通常的静态图像格式,如BMP、PNG、JPEG、TIFF均只能针对整幅图像进行编码,不支持把页边空部分排除在有效编码范围之外:

    • BMP、JPEG是根本就没有与裁边相关的数据结构,PNG的oFFs chunk虽然定义了图像的x、y向偏移,但没有定义子图像尺寸,所以不能用于裁边。
    • TIFF正式的标准规范《TIFF Revision 6.0》中没有与裁边相关的规定,但开源项目libtiff中为了处理大型图像,自行扩展定义了TIFFTAG_PIXAR_IMAGEFULLWIDTH、TIFFTAG_PIXAR_IMAGEFULLLENGTH这两个tag,与标准中已经定义的TIFFTAG_XPOSITION、TIFFTAG_YPOSITION联合起来,理论上说可满足裁边图像的存储,问题是这两个扩展tag毕竟不在标准范围内,相当于自说自话,与自己定义一种新图像格式差不多,支持的软件几乎没有,所以没啥实用性。

    而在支持动画的图像格式,如GIF、WEBP中,却都有相关的定义,原因很简单:动画为了提高压缩效率,通常需要进行运动分析,简单点说,从这一帧到下一帧如果背景没有变化,那么在下一帧就不需要存储背景区域,只需存储有变化的区域即可,因此动画图像格式一般都允许一幅(一帧)图像中只存储有实际意义的中央区域,周围区域不参与编码、存储。

    其中WEBP的动画格式与GIF不同,GIF的静态图像、动画在文件格式上没有区别,但WEBP的静态图像、动画在格式定义上差别很大,WEBP发明人兼格式定义者Google提供的官方开源项目libwebp中,解码静态图像和动画也是各不相混的两套API、两套示例代码,所以用动画WEBP存储静态的裁边图像,在技术上可行,但在兼容性上存在较大的不可预测性,并不是一个稳妥的选择。

    因此在ComicEnhancerPro(CEP)中选择GIF文件作为存储裁边图像的载体——从CEP v6.02开始,在界面上提供“裁减GIF”选项,用户如果勾选此选项,则所存储的GIF文件将自动进行裁边,把无实际内容的页边空部分排除在外,只存储有实际内容的版心部分,以减小最终文件长度。

    之所以要由用户自己选择,并且缺省情况下该选项还未被选中,是因为GIF标准的实际约束力太弱,很多软件在实际解码GIF图像时基本上是老子高兴怎样就怎样,标准算根毛线?结果就造成了一个兼容性问题:未参与编码的页边空部分,在解码时应该用什么颜色来填充?

    这个问题本来不应该是问题,因为在GIF格式规范《GIF89a Specification》中规定得很清楚,应该用Logical Screen Descriptor中规定的Background Color来填充,原文如下:

     vii) Background Color Index - Index into the Global Color Table for the Background Color. The Background Color is the color used for those pixels on the screen that are not covered by an image. If the Global Color Table Flag is set to (zero), this field should be zero and should be ignored.

    但实际测试下来发现,Edge、IE中都把这部分填充为透明色,而不是背景色。所以如果要在网页浏览器中显示这种裁边GIF,最好在CEP中除“裁减GIF”外,还同时勾选“色彩”选项中的“索引色PNG、GIF设置背景透明”,这样生成的GIF图像在网页浏览器中看起来才天衣无缝。

    至于Photoshop 2020则很奇葩:裁左、右、下都可以,都会按照标准规定的那样用背景色填充裁掉的页边空,但唯独不能裁上部,只要裁了上部,即使只裁了一行扫描线,整个图像就全部用背景色填充,不会再显示版心内容。

    ACDSee 2022旗舰版则与GIF标准拧着来的:显示这种裁边GIF的时候,根本就不会显示页边空部分,只会显示裁减后的版心部分,即完全忽略Logical Screen Descriptor部分。

    正是因为有以上这些问题,所以最终是否要用GIF存储裁边图像的权利必须完全交给用户:如果用户使用的软件工具都支持这种裁边GIF(例如我开发的图像浏览软件都支持,主流网页浏览器也都支持),那么勾选“裁减GIF”选项可以在页边空占比较多时有效减小最终GIF文件长度,否则还是别勾选了,兼容性优先吧。

    另外即使勾选了“裁减GIF”选项,也还有以下限制:

    1. GIF标准只支持调色板图像,不支持24位真彩图像。如果在CEP中对24位真彩图像选择了GIF存储,则自动转换成256色再存储。
    2. GIF压缩算法是LZW无损算法,这种算法按照Adobe公司在《PDF Reference》中的说明,压缩效率在通常情况下不如PNG使用的Flat无损压缩算法。如果是纯黑白图像,那LZW更是拍马也追不上TIFF文件所采用的CCITT G4无损压缩算法。所以如果页边空在整幅图像中的占比不高,裁减页边空所带来的文件长度减小,可能还不如其他文件格式的高效压缩算法所带来的减小更明显。正好CEP的“扫描书籍处理”界面的“输出”页签中,点击“输出”按钮就可以看到文件的实际输出效果和输出文件长度,所以可以逐一选择不同的文件格式做比较,选择针对具体图像最优的一种。

    如果对具体的软件代码感兴趣,那么在CEP中是这样存储裁边图像的:

    1、图像的完整尺寸记录在Logical Screen Descriptor的Logical Screen Width、Logical Screen Height里,并且使用全局调色板——不仅因为Background Color Index是针对全局调色板的,更因为本来就只有一帧图像,完全没有必要使用局部调色板。基于giflib的关键代码如下:

    	// 填写GIF画布
    	GifFile->SWidth = nWidth;	// 完整页面宽度
    	GifFile->SHeight = nHeight;	// 完整页面高度
    	// 按照图像实际使用的颜色数设置最小位数,提高编码效率
    	GifFile->SColorResolution = GifBitSize(nRealColors);
    	GifFile->SBackGroundColor = *canvas;	// 取图像左上角颜色为背景色
    	// 填写调色板
    	GifFile->SColorMap = GifMakeMapObject((1 << GifFile->SColorResolution), NULL);
    	for (int i = 0; i < nRealColors; i++)
    	{
    		const RGBQUAD c = ppal[i];
    		GifFile->SColorMap->Colors[i].Red = c.rgbRed;
    		GifFile->SColorMap->Colors[i].Green = c.rgbGreen;
    		GifFile->SColorMap->Colors[i].Blue = c.rgbBlue;
    	}
    
    	// EGifPutScreenDesc会破坏GifFileOut->SColorMap,所以调用前必须先备份,否则出现野指针
    	ColorMapObject* SColorMap = GifFile->SColorMap;
    	if (EGifPutScreenDesc(GifFile,
    		GifFile->SWidth,
    		GifFile->SHeight,
    		GifFile->SColorResolution,
    		GifFile->SBackGroundColor,
    		GifFile->SColorMap) == GIF_ERROR) {
    			GifFreeMapObject(SColorMap);
    			goto End;
    	}
    	// 在EGifPutScreenDesc中重新分配了GifFileOut->SColorMap,所以手工释放前面保存的
    	GifFreeMapObject(SColorMap);
    

    2、裁减后的版心尺寸、版心位置记录在Image Descriptor的Image Width、Image Height、Image Left Position、Image Top Position里。基于giflib的关键代码如下:

    	// 裁边
    	int nFrameLeft, nFrameTop, nFrameWidth, nFrameHeight;
    	nFrameLeft = nFrameTop = 0;
    	nFrameWidth = GifFile->SWidth;
    	nFrameHeight = GifFile->SHeight;
    	if (bCrop)
    		GetFrameFromCanvas(canvas, nFrameLeft, nFrameTop, nFrameWidth, nFrameHeight);
    	// 存储图像帧
    	GifImageDesc imageDesc;
    	memset(&imageDesc, 0, sizeof(GifImageDesc));
    	imageDesc.Left = nFrameLeft;
    	imageDesc.Top = nFrameTop;
    	imageDesc.Width = nFrameWidth;
    	imageDesc.Height = nFrameHeight;
    
    	if (EGifPutImageDesc(GifFile, imageDesc.Left, imageDesc.Top,
    		imageDesc.Width, imageDesc.Height, imageDesc.Interlace, 
    		imageDesc.ColorMap) == GIF_ERROR )
    		goto End;	

    三、排除页边空后的GIF文件转PDF

    在我开发的软件工具链里,除了图像处理、图像浏览工具外,还包括图像转PDF、从PDF中提取图像等工具,因此除了裁边GIF的编码、解码外,还必须考虑裁边GIF转PDF的问题。

    从技术上说,PDF中只存储裁边后的版心图像完全没有问题——在PDF中,页面尺寸定义与页面中的图像显示是分离的,因此按照扫描页面的完整尺寸定义PDF页面尺寸,然后按照版心在原页面中的位置、大小显示版心图像即可。事实上这种情况在使用虚拟打印机把图像转PDF的时候经常出现:如果图像的纵横比例与打印时所选的纸张纵横比例不同,打印出来的PDF页面的上下或者左右就会出现多余的空白,这些空白会被PDF浏览器按照用户所选底色进行填充。

    但由于我不仅要考虑图像转PDF,还要考虑反向的从PDF中提取图像,所以最终做出的决定也很简单:在FreePic2Pdf、Pdg2Pic中,所有裁边GIF一律转换成常规GIF再转PDF,即在转出来的PDF文件中,所有被裁减掉的部分都会被补上。这样虽然会造成PDF文件长度略有增加,但却能给反向的PDF图像提取工作减少更多的麻烦。至于PDF文件长度增加——反正我自己从来不看、不收这种扫描图像版PDF,我只看扫描版PDG,包括GIF文件直接改名为PDG。

    (完)

  • 相关阅读:
    jquery另外一种类似tab切换效果
    简单的Tab切换组件
    switchable图片切换
    web前端性能优化总结
    iframe之间通信问题及iframe自适应高度问题
    javascript cookie
    grunt项目构建工具
    input全选与单选(把相应的value放入隐藏域去)
    Ajax跨域问题
    Jquery回到顶部功能
  • 原文地址:https://www.cnblogs.com/stronghorse/p/16294453.html
Copyright © 2020-2023  润新知