简介

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 的图片使用的是 YCrCb 颜色模型(YUV颜色编码,Y”表示明亮度(luminance、Luma),“U”和“V”则是色度、浓度(Chrominance、Chroma)),而不是计算机上最常用的 RGB。以每个点保存一个8bit的亮度值,每2x2个点保存一个Cr Cb值,而图象在肉眼中的感觉不会起太大的变化。所以,原来用 RGB 模型,4个点需要 4x3=12字节。而现在仅需要4+2=6字节,平均每个点占12bit。当然JPEG格式里允许每个点的C值都记录下来。不过 MPEG 里都是按12bit一个点来存放的,我们简写为 YUV12。

    YUV分量可以由PAL制系统中归一化(经过伽马校正)的R’,G’,B’经过下面的计算得到:

    Y=0.299R'+0.587G'+0.114B'
    U=-0.147R'-0.289G'+0.436B'
    V=0.615R'-0.515G'-0.100B'

    ITU-R版本的公式:

    Y=0.299*R+0.587*G+0.114*B (亮度)
    Cb=-0.1687*R-0.3313*G+0.5*B+128
    Cr=0.5*R-0.4187*G-0.0813*B+128
    
    R=Y+1.402*(Cr-128)
    G=Y-0.34414*(Cb-128)-0.71414*(Cr-128)
    B=Y+1.772*(Cb-128)
  • 缩减取样(Downsampling)

    上面所作的转换使下一步骤变为可能,也就是减少U和V的成份(称为”缩减取样”或”色度抽样”(chroma subsampling)。在JPEG上这种缩减取样的比例可以是4:4:4(无缩减取样),4:2:2(在水平方向2的倍数中取一个),以及最普遍的4:2:0(在水平和垂直方向2的倍数中取一个)。对于压缩过程的剩余部份,Y、U、和V都是以非常类似的方式来个别地处理。

  • 离散余弦变换(discrete cosine transform)
    将视频中的每个成份(Y,U,V)生成三个区域,每一个区域再划分成如瓷砖般排列的一个个的8×8子区域,每一子区域使用二维的离散余弦变换(DCT)转换到频率空间。经过这个变换,就把图片里点和点间的规律呈现出来了,更方便压缩。JPEG 里是对每8x8个点为一个单位处理的。所以如果原始图片的长宽不是8的倍数,都需要先补成8的倍数,好一块块的处理。另外,记得CrCb都是2x2记录一次吗? 所以大多数情况。是要补成16x16的整数块。按从左到右,从上到下的次序排列 (和我们写字的次序一样)。JPEG 里是对YCrCb分别做DCT变换的。

    JPEG 编码时使用的是 Forward DCT (FDCT) 解码时使用的 Inverse DCT(IDCT),这个步骤很花时间,另外有种 AA&N 优化算法,在Intel主页上可以找到AA&N IDCT MMX优化代码。

    左上角之相当大的数值称为DC系数(直流系数);其他63个值称为AC系数(交流系数)。下面将对所有8×8表格中的DC系数使用差分编码,对AC系数使用进程编码。

  • 重排列 DCT 结果

    DCT将一个8x8的数组变换成另一个8x8的数组。但是内存里所有数据都是线形存放的,如果我们一行行的存放这 64 个数字,每行的结尾的点和下行开始的点就没有什么关系,所以JPEG规定按如下次序整理64个数字。

    UINT16 requant[DCTSIZE2] =
    {
        0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63
    }

    这样数列里的相邻点在图片上也是相邻的了。

  • 量化(Quantization)

    对于前面得到的64个空间频率振幅值,我们将对它们作幅度分层量化操作。方法就是分别除以量化表里对应值并四舍五入。

    UINT16 requant[DCTSIZE2] =
    {
        16,69,87,80,68,109,103,77,64,81,104,113,92,78,121,120,101,72,95,98,112,100,99,}
    
    for (i = 0 ; i<=63; i++ )
        vector[i] = (int) (vector[i] / quantval[i] + 0.5)

    这张表依据心理视觉阀制作,对 8bit 的亮度和色度的图象的处理效果不错。当然我们可以使用任意的量化表。量化表是定义在jpeg的DQT标记后。一般为Y值定义一个,为 C 值定义一个。量化表是控制JPEG压缩比的关键。这个步骤除掉了一些高频量,损失了很高细节。但事实上人眼对高空间频率远没有低频敏感。所以处理后的视觉损失很小。另一个重要原因是所有的图片的点与点之间会有一个色彩过渡的过程。大量的图象信息被包含在低空间频率中。经过量化处理后,在高空间频率段,将出现大量连续的零。

  • 编码(Coding)

    颜色转换完成到编码之前,图像并没有得到进一步的压缩,DCT变换和量化可以说是为编码阶段做准备。

    编码采用两种机制:一是0值的行程长度编码;二是熵编码(EntropyCoding)

    • ①之字形排序(Zig-zag ordering)
    • ②使用RLE对交流系数(AC)进行编码
    • ③使用DPCM对直流系数(DC)进行编码
    • ④熵编码

    编码实际上是一种基于统计特性的编码方法。在JPEG中允许采用HUFFMAN编码或者算术编码。而基线JPEG算法(baseline sequential)采用的是前者。

    JPEG的压缩模式有以下几种:

    • 顺序式编码(Sequential Encoding):(基于DCT)一次将图像由左到右、由上到下顺序处理;
    • 递增式编码(Progressive Encoding):(基于DCT)当图像传输的时间较长时,可将图像分数次处理,以从模糊到清晰的方式来传送图像(效果类似GIF在网络上的传输);
    • 无有损编码(Lossless Encoding):(基于DPCM)保证解码后完全精确恢复到原图像采样值;
    • 阶梯式编码(Hierarchical Encoding):图像以数种分辨率来压缩,其目的是为了让具有高分辨率的图像也可以在较低分辨率的设备上显示。

文件结构

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)的一般顺序为:

标记 标记码 作用
SOI 0xFFD8 图像开始
APP0 0xFFE0 JFIF应用数据块
APPn 0xFFEn 其他的应用数据块(n,1~15)
DQT 0xFfdb 量化表
SOF0 0xFFC0 帧图像开始
DHT 0xFFC4 霍夫曼(Huffman)表
SOS 0xFFDA 扫描线开始
压缩图像数据
EOI 0xFFD9 图像结束

所以我们可以看到-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的格式呈现。如下图所示:

JPEG图片解码的更多相关文章

  1. Canvas与图片压缩的示例代码

    本篇文章主要介绍了Canvas与图片压缩的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. ios – 我可以使用AVCaptureSession将AAC流编码到内存吗?

    解决方法我最后向Apple寻求建议.似乎AVCaptureSession抓住了AAC硬件编码器,但只允许您使用它直接写入文件.您可以使用软件编码器,但您必须专门询问它而不是使用AudioConverterNew:同当然,软件编码器会占用cpu资源,但会完成工作.

  3. ios – NSString cString已被弃用.什么是替代品?

    我有另一个新手问题.我写了一段代码,将Nsstring转换为NSMutableData,以模拟一个webService结果.但事实证明,cString已被弃用.你可以帮我更换吗?这是我的代码解决方法>从字符串获取原始字节.>获取UTF8编码中这些字节的长度.>使用dataWithBytes:length:方法创建NSData对象.

  4. 在iOS上保存修改后的元数据(无需重新编码)的原始图像数据

    我想在temp文件夹中保存一些元数据更改的图像,而无需重新编码实际的图像数据.我发现能够做到这一点的唯一方法是ALAssetsLibrary/writeImageDataToSavedPhotosAlbum:metadata:completionBlock:,但是,这个方法将图像保存到照片库中.相反,我想将图像保存到临时文件夹(例如,通过电子邮件分享,而不填充照片库).我试过使用CGImageDe

  5. ios4 – xcode上的奇怪错误:解析问题Unknow类型名称’plementation’

    在线上:Xcode4说:错误解析问题UnkNow类型名称’plementation’之后有很多解析问题.但该项目在iPhone上工作.我真的不知道是什么…id=27对于我自己的项目,我用TextWrangler打开了违规文件,并用“Western”编码重新保存它们,到目前为止,还没有从LLVM/Clang得到任何进一步的问题.

  6. ios – 如何删除/解码URL百分比编码?

    我想要一个url并将其转换成更易读的格式.例如我有以下链接:我拿走了不必要的部分,并留下了“Sándor_Font”作为Nsstring.有没有什么方式将它转换成“SándorFont”,而不必输出每一个特殊字符的组合并替换字符串的每个部分?为了演示如何使用它,我写了以下示例代码:最后我要标签说“SándorFont”不是“Sándor_Font”.谢谢!

  7. iOS UIImage存储格式,内存使用和编码/解码

    iOS如何存储从压缩数据(jpeg2000,png,jpg等)加载的图像示例:[UIImageimageWithData:pngData]它是在内部存储实际编码的字节并按需解压缩,还是永久解压缩为原始像素或其他格式?解决方法我在iPad2上创建了一个测试应用程序,使用以下三种方法加载了200个384×384像素的jpeg2000图像文件(117,964,800字节的原始像素):[UIImageim

  8. 为swift编码引入map()和flatMap(), map those arrays

    ,一旦将一个不合法的值赋给item.url(NSURL!于是我们想到了使用guard语句的安全操作,如果出现了异常数据,我们就返回一个nil的ListItem对象,去避免返回一个残缺的ListItem对象,同时避免了crash。使用flatMap()好了,这时候flatMap()就来解决这个问题了。flatMap()与map()其实非常相似,map()的规则是T->U,而flatMap()的规则是T->U?,而且如果转换的结果是nil的话,flatMap()是不会将其添加到输出的array中的。因此可以说

  9. 使用Swift快速查看Unicode编码

    Swift中的String完全支持Unicode,它可以使用一个Unicodescalar来构造一个字符串。Unicode编码表很大,查询时有一定的困难。Swift有一个对应的数据类型,UnicodeScalar,可以直接打印其字符。变量start指定了Unicodescalar的起始范围,counts指定要打印10个。hex存有转换为大写十六进制的Unicodescalar的值。在print函数中,先将0x1F493传入UnicodeScalar类,以构造一个UnicodeScalar实例,然后将其按指

  10. Swift3.0语言教程删除字符与处理字符编码

    Swift3.0语言教程删除字符与处理字符编码Swift3.0语言教程删除字符Swift3.0语言教程删除字符与处理字符编码,在字符串中,如果开发者有不需要使用的字符,就可以将这些字符删除。importFoundationvara=Nsstringprintvarb:CharacterSet=NSCharacterSet.whitespacesAndNewlinesasCharacterSetprint//去除空格和回车运行结果如下:删除前:Hello删除后:HelloSwift3.0语言教程处理字符编码

随机推荐

  1. 【cocos2d-x 3.x 学习笔记】对象内存管理

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部