JPEG图片解码
简介 JPEG是一种广泛适用的压缩图像标准方式。JPEG就是「联合图像专家组」(JointPhotographicExpertsGroup)的首字母缩写。采用这种压缩格式的文件一般就称为JPEG;此类文件的一般扩展名有:.jpeg、.jfif、.jpg或.jpe,其中在主流平台最常见的是.jpg。 JPEG/JFIF是互联网上最常见的图像存储和传送格式。但此格式不适合用来绘制线条、文字或图标,因为它的压缩方式对这几种图片损坏严重。PNG和GIF文件更适合以上几种图片。不过GIF每像素只支持8bits色深,不适合色彩丰富的照片,但PNG格式就能提供JPEG同等甚至更多的图像细节。 在 Photoshop软件中以JPEG格式储存时,提供11级压缩级别,以0—10级表示。其中0级压缩比最高,图像品质最差。即使采用细节几乎无损的10 级质量保存时,压缩比也可达 5:1。以BMP格式保存时得到4.28MB图像文件,在采用JPG格式保存时,其文件仅为178KB,压缩比达到24:1。经过多次比较,采用第8级压缩为存储空间与图像质量兼得的最佳比例。 JPEG本身只有描述如何将一个视频转换为字节的数据流(streaming),但并没有说明这些字节如何在任何特定的存储媒体上被封存起来。JPEG的压缩方式通常是破坏性数据压缩(lossy compression),意即在压缩过程中图像的质量会遭受到可见的破坏,有一种以JPEG为基础的标准Lossless JPEG是采用无损的压缩方式,但Lossless JPEG并没有受到广泛的支持。 JPEG的压缩编码流程:
文件结构 JPEG文件大体上可以分成两个部分:标记码(Tag)和压缩数据,JPEG文件格式中,一个字(16位)的存储使用的是Motorola格式,而不是Intel格式。也就是说,一个字的高字节(高8位)在数据流的前面,低字节(低8位)在数据流的后面,与平时习惯的Intel格式不一样。 标记码由两个字节构成,其前一个字节是固定值0xFF,后一个字节则根据不同意义有不同数值。在每个标记码之前还可以添加数目不限的无意义的0xFF填充,也就说连续的多个0xFF可以被理解为一个0xFF,并表示一个标记码的开始。而在一个完整的两字节的标记码后,就是该标记码对应的压缩数据流,记录了关于文件的诸种信息。常用的标记有SOI、APP0、DQT、SOF0、DHT、DRI、SOS、EOI。 完成的JPEG标记表内包含很多的标记码,有需要的可以去查阅相关的资料,这里简单地介绍下几个常用的标记。 JFIF格式的JPEG文件(*.jpg)的一般顺序为:
所以我们可以看到-x里面jpg格式的判断函数: bool Image::isJpg(const unsigned char * data,ssize_t dataLen) { if (dataLen <= 4) { return false; } static const unsigned char JPG_SOI[] = {0xFF,0xD8}; return memcmp(data,JPG_SOI,2) == 0; } 关于jpeg标记码的详细信息这里先不介绍,后面手动压缩jpeg图片的时候再详细分析下。 //cocos2dx libjpg的解码 bool Image::initWithJpgData(const unsigned char * data,ssize_t dataLen) { #if CC_USE_JPEG /* these are standard libjpeg structures for reading(decompression) */ struct jpeg_decompress_struct cinfo; /* We use our private extension JPEG error handler. * Note that this struct must live as long as the main JPEG parameter * struct,to avoid dangling-pointer problems. */ struct MyErrorMgr jerr; /* libjpeg data structure for storing one row,that is,scanline of an image */ JSAMPROW row_pointer[1] = {0}; unsigned long location = 0; bool ret = false; do { /* We set up the normal JPEG error routines,then override error_exit. */ //设置异常处理 cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = myErrorExit; /* Establish the setjmp return context for MyErrorExit to use. */ if (setjmp(jerr.setjmp_buffer)) { /* If we get here,the JPEG code has signaled an error. * We need to clean up the JPEG object,close the input file,and return. */ jpeg_destroy_decompress(&cinfo); break; } /* setup decompression process and source,then read JPEG header */ //初始化jpeg_decompress_struct类型结构体,libjpeg内部使用 jpeg_create_decompress( &cinfo ); #ifndef CC_TARGET_QT5 jpeg_mem_src(&cinfo,const_cast<unsigned char*>(data),dataLen); #endif /* CC_TARGET_QT5 */ /* reading the image header which contains image information */ #if (JPEG_LIB_VERSION >= 90) // libjpeg 0.9 adds stricter types. jpeg_read_header(&cinfo,TRUE); #else jpeg_read_header(&cinfo,TRUE); #endif // we only support RGB or grayscale //设置输出的颜色格式,cocos2dx 3.2的版本暂时支持RGB和I8的格式 if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { _renderFormat = Texture2D::PixelFormat::I8; }else { cinfo.out_color_space = JCS_RGB; _renderFormat = Texture2D::PixelFormat::RGB888; } /* Start decompression jpeg here */ //开始解码 jpeg_start_decompress( &cinfo ); /* init image info */ //初始化成员变量 _width = cinfo.output_width; _height = cinfo.output_height; _hasPremultipliedAlpha = false; //申请内存 _dataLen = cinfo.output_width*cinfo.output_height*cinfo.output_components; _data = static_cast<unsigned char*>(malloc(_dataLen * sizeof(unsigned char))); CC_BREAK_IF(! _data); /* now actually read the jpeg into the raw buffer */ /* read one scan line at a time */ //逐行扫描像素信息,存入指定的内存当中 while (cinfo.output_scanline < cinfo.output_height) { row_pointer[0] = _data + location; location += cinfo.output_width*cinfo.output_components; jpeg_read_scanlines(&cinfo,row_pointer,1); } /* When read image file with broken data,jpeg_finish_decompress() may cause error. * Besides,jpeg_destroy_decompress() shall deallocate and release all memory associated * with the decompression object. * So it doesn't need to call jpeg_finish_decompress(). */ //jpeg_finish_decompress( &cinfo ); //释放内存 jpeg_destroy_decompress( &cinfo ); /* wrap up decompression,destroy objects,free pointers and close open files */ ret = true; } while (0); return ret; #else return false; #endif // CC_USE_JPEG } libjpeg-turbo libjpeg-turbo是一种使用了SIMD指令(MMX,SSE2,NEON)来加快图片在x86,x86-64以及其他ARM系统上的基线压缩和解压的JPEG解码器,是libjpeg的一个扩展。在上述系统中,在其他条件相同的情况下,libjpeg-turbo是libjpeg的2-4倍速度。在其他类型的系统中,libjpeg-turbo同样可以通过高度优化的哈夫曼编码程序比libjpeg速度更快。在很多情况下,libjpeg-turbo都是非常好用的jpeg解码器。 详细的介绍可以去libjpeg-turbo的主页。 可以自己编译源码,也可以下载已经编译好的链接库。导入到cocos2dx中非常简单,添加libjpeg.a和libjpeg-turbo.a两个链接库,包含相应的include文件夹即可。 BPG BPG(全称 Better Portable Graphics),它由著名的法国程序员 Fabrice Bellard(FFmpeg和QEMU的作者)设计并提出。其优势在于具有更高的压缩率,在相同图像质量下,BPG文件的大小只有JPEG的一半。此外它还原生支持8位和16位通道。 详细的介绍可以去Bellard主页查看。 我下载的源码进行了编译,在文件大小和图像质量的表现上都很不错,不过目前0.9.5版本不支持x86-64架构,期待作者以后的更新。 扩展链接 1、YUV颜色编码 Y’UV的发明是由于彩色电视与黑白电视的过渡时期[1]。黑白视讯只有Y(Luma,Luminance)视讯,也就是灰阶值。到了彩色电视规格的制定,是以YUV/YIQ的格式来处理彩色电视图像,把UV视作表示彩度的C(Chrominance或Chroma),如果忽略C信号,那么剩下的Y(Luma)信号就跟之前的黑白电视信号相同,这样一来便解决彩色电视机与黑白电视机的相容问题。Y’UV最大的优点在于只需占用极少的带宽。 彩色图像记录的格式,常见的有RGB、YUV、CMYK等。彩色电视最早的构想是使用RGB三原色来同时传输。这种设计方式是原来黑白带宽的3倍,在当时并不是很好的设计。RGB诉求于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度,Y代表的是亮度,UV代表的是彩度(因此黑白电影可省略UV,相近于RGB),分别用Cr和Cb来表示,因此YUV的记录通常以Y:UV的格式呈现。如下图所示: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |