• 音视频入门-16-使用libjpeg-trubo处理JPEG图片


    * 音视频入门文章目录 *

    RGB-to-JPEG 回顾

    上一篇 【手动生成一张JPEG图片】 根据 【JPEG文件格式详解】 一步一步地实现了将 RGB 数据生成了一张 JPEG 图片。

    可以感受到,自己来实现 JPEG 的基本系统编码还是有相当的复杂度的,JPEG 压缩编码算法一共分为 11 个步骤:

    1. 颜色模式转换
    2. 采样
    3. 分块
    4. 离散余弦变换(DCT)
    5. 量化
    6. Zigzag 扫描排序
    7. DC 系数的差分脉冲调制编码
    8. DC 系数的中间格式计算
    9. AC 系数的游程长度编码
    10. AC 系数的中间格式计算
    11. 熵编码

    下面,我们使用 libjpeg-turbo 来处理 JPEG 图片。

    使用 libjpeg-turbo

    Building libjpeg-turbo

    官方 Build 文档

    mkdir libjpeg-turbo/build
    cd libjpeg-turbo/build
    
    cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH=./ -DCMAKE_INSTALL_BINDIR:PATH=./ -DCMAKE_INSTALL_DATAROOTDIR:PATH=./ -DCMAKE_INSTALL_DOCDIR:PATH=./ -DCMAKE_INSTALL_LIBDIR:PATH=./  -DCMAKE_INSTALL_INCLUDEDIR:PATH=./  -DCMAKE_INSTALL_MANDIR:PATH=./ ..
    
    make
    make install
    

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.14)
    
    set(CMAKE_C_STANDARD 99)
    
    #aux_source_directory(../3rd/libjpeg-turbo LIBJPEG_TURBO_SRC)
    
    link_directories(../3rd/libjpeg-turbo/build)
    include_directories(../3rd/libjpeg-turbo/build)
    
    add_executable(16-rgb-to-jpeg-library rgb-to-jpeg-with-libjpeg-turbo.c util.c)
    
    # 添加链接库
    target_link_libraries(16-rgb-to-jpeg-library libturbojpeg.dylib)
    

    libjpeg-turbo 处理 JPEG 图片

    JPEG to RGB24

    使用 libjpeg-turbo 解码 JPEG 图片成 RGB 格式的数据。

    代码中使用的 JPEG 图片

    struct ImageData {
        unsigned char *pixels;
        long  width;
        long height;
    };
    
    int decode_JPEG_file(char *inJpegName, char *outRgbName) {
        struct jpeg_decompress_struct cinfo;
        struct jpeg_error_mgr jerr;
    
        FILE * infile;
        FILE * outfile;
    
        if ((infile = fopen(inJpegName, "rb")) == NULL) {
            fprintf(stderr, "can't open %s
    ", inJpegName);
            return -1;
        }
        if ((outfile = fopen(outRgbName, "wb")) == NULL) {
            fprintf(stderr, "can't open %s
    ", outRgbName);
            return -1;
        }
    
        cinfo.err = jpeg_std_error(&jerr);
    
        jpeg_create_decompress(&cinfo);
    
        jpeg_stdio_src(&cinfo, infile);
    
        jpeg_read_header(&cinfo, TRUE);
    
        printf("image_width = %d
    ", cinfo.image_width);
        printf("image_height = %d
    ", cinfo.image_height);
        printf("num_components = %d
    ", cinfo.num_components);
        printf("enter scale M/N:
    ");
    
        jpeg_start_decompress(&cinfo);
    
        //输出的图象的信息
        printf("output_width = %d
    ", cinfo.output_width);
        printf("output_height = %d
    ", cinfo.output_height);
        printf("output_components = %d
    ", cinfo.output_components);
    
        int row_stride = cinfo.output_width * cinfo.output_components;
        /* Make a one-row-high sample array that will go away when done with image */
        JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
        buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);
    
        struct ImageData imageData = {
            .width =  cinfo.image_width,
            .height = cinfo.image_height,
            .pixels = malloc(row_stride*cinfo.image_height)
        };
        long counter = 0;
    
        while (cinfo.output_scanline < cinfo.output_height) {
            jpeg_read_scanlines(&cinfo, buffer, 1);
            memcpy(imageData.pixels + counter, buffer[0], row_stride);
            counter += row_stride;
        }
    
        printf("total size: %ld
    ", counter);
        fwrite(imageData.pixels,  counter, 1, outfile);
    
    
        jpeg_finish_decompress(&cinfo);
    
        jpeg_destroy_decompress(&cinfo);
    
        fclose(infile);
        fclose(outfile);
        free(imageData.pixels);
    
        return 0;
    }
    
    int main(int argc, char* argv[]) {
        printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to RGB24 ↓↓↓↓↓↓↓↓↓↓
    ");
        char *inJpegName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
        char *outRgbName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.rgb24";
        int flag1 = decode_JPEG_file(inJpegName1, outRgbName1);
        if (flag1 == 0) {
            printf("decode ok!
    ");
        } else {
            printf("decode error!
    ");
        }
        printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to RGB24 ↑↑↑↑↑↑↑↑↑↑
    
    ");
    }
    

    运行上面的代码,将得到解码后的 RGB 文件 libjpeg-turbo-test-image.rgb24 :

    使用 ffplay 查看 RGB24 文件:

    ffplay -f rawvideo -pixel_format rgb24  -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.rgb24
    

    jpeg-to-rgb24-example

    RGB24 to JPEG

    // 彩虹的七种颜色
    uint32_t rainbowColors[] = {
            0XFF0000, // 红
            0XFFA500, // 橙
            0XFFFF00, // 黄
            0X00FF00, // 绿
            0X007FFF, // 青
            0X0000FF, // 蓝
            0X8B00FF  // 紫
    };
    
    void genRGB24Data(uint8_t *rgbData, int width, int height) {
        for (int i = 0; i < width; ++i) {
            // 当前颜色
            uint32_t currentColor = rainbowColors[0];
            if(i < 100) {
                currentColor = rainbowColors[0];
            } else if(i < 200) {
                currentColor = rainbowColors[1];
            } else if(i < 300) {
                currentColor = rainbowColors[2];
            } else if(i < 400) {
                currentColor = rainbowColors[3];
            } else if(i < 500) {
                currentColor = rainbowColors[4];
            } else if(i < 600) {
                currentColor = rainbowColors[5];
            } else if(i < 700) {
                currentColor = rainbowColors[6];
            }
            // 当前颜色 R 分量
            uint8_t R = (currentColor & 0xFF0000) >> 16;
            // 当前颜色 G 分量
            uint8_t G = (currentColor & 0x00FF00) >> 8;
            // 当前颜色 B 分量
            uint8_t B = currentColor & 0x0000FF;
    
            for (int j = 0; j < height; ++j) {
                int currentIndex = 3*(i*height+j);
                rgbData[currentIndex] = R;
                rgbData[currentIndex+1] = G;
                rgbData[currentIndex+2] = B;
            }
        }
    }
    
    int encode_JPEG_file(char *strImageName,uint8_t *image_buffer, int image_height, int image_width, int quality) {
    
        struct jpeg_compress_struct cinfo;
        
        struct jpeg_error_mgr jerr;
        /* More stuff */
        FILE * outfile;     /* target file */
        JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */
        int row_stride;     /* physical row width in image buffer */
        
        cinfo.err = jpeg_std_error(&jerr);
        /* Now we can initialize the JPEG compression object. */
        jpeg_create_compress(&cinfo);
    
       
        if ((outfile = fopen(strImageName, "wb")) == NULL) {
            fprintf(stderr, "can't open %s
    ", strImageName);
            //exit(1);
            return -1;
        }
        jpeg_stdio_dest(&cinfo, outfile);
    
      
        cinfo.image_width = image_width;    /* image width and height, in pixels */
        cinfo.image_height = image_height;
        cinfo.input_components = 3;     /* # of color components per pixel */
        cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */
     
        jpeg_set_defaults(&cinfo);
       
        jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
        
        jpeg_start_compress(&cinfo, TRUE);
    
     
        row_stride = image_width * 3;   /* JSAMPLEs per row in image_buffer */
    
        int line = 0;
        while (line < cinfo.image_height) {
          
            //row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
            row_pointer[0] = &image_buffer[line * row_stride];
            jpeg_write_scanlines(&cinfo, row_pointer, 1);
    
            line++;
        }
    
        /* Step 6: Finish compression */
        jpeg_finish_compress(&cinfo);
        /* After finish_compress, we can close the output file. */
        fclose(outfile);
    
        /* Step 7: release JPEG compression object */
        /* This is an important step since it will release a good deal of memory. */
        jpeg_destroy_compress(&cinfo);
    
        return 0;
    }
    
    int main(int argc, char* argv[]) {
        printf("↓↓↓↓↓↓↓↓↓↓ Encode RGB24 to JPEG ↓↓↓↓↓↓↓↓↓↓
    ");
        int width = 700, height = 700;
        char *outJpegName2 = "/Users/staff/Desktop/rainbow-rgb24.jpeg";
        //uint8_t rgbBuffer[width*height*3];
        uint8_t *rgbBuffer = malloc(width*height*3);
        genRGB24Data(rgbBuffer, width, height);
        int flag2 = encode_JPEG_file(outJpegName2, rgbBuffer, width, height, 80);
        if (flag2 == 0) {
            printf("encode ok!
    ");
        } else {
            printf("encode error!
    ");
        }
        free(rgbBuffer);
        printf("↑↑↑↑↑↑↑↑↑↑ Encode RGB24 to JPEG ↑↑↑↑↑↑↑↑↑↑
    
    ");
    }
    

    仍然是生成彩虹图,运行上面的代码,将得到编码后的 JPEG 文件 rainbow-rgb24.jpeg :

    rainbow-rgb24.jpeg

    JPEG to YUV

    使用 libjpeg-turbo 解码 JPEG 图片成 YUV 格式的数据。

    int tjpeg2yuv(unsigned char* jpeg_buffer, int jpeg_size, unsigned char** yuv_buffer, int* yuv_size, int* yuv_type)
    {
        tjhandle handle = NULL;
        int width, height, subsample, colorspace;
        int flags = 0;
        int padding = 1; // 1或4均可,但不能是0
        int ret = 0;
    
        handle = tjInitDecompress();
        tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);
    
        printf("w: %d h: %d subsample: %d color: %d
    ", width, height, subsample, colorspace);
    
        flags |= 0;
    
        *yuv_type = subsample;
        // 注:经测试,指定的yuv采样格式只对YUV缓冲区大小有影响,实际上还是按JPEG本身的YUV格式来转换的
        *yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
        *yuv_buffer =(unsigned char *)malloc(*yuv_size);
        if (*yuv_buffer == NULL)
        {
            printf("malloc buffer for rgb failed.
    ");
            return -1;
        }
        ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, *yuv_buffer, width,
                                 padding, height, flags);
        if (ret < 0)
        {
            printf("compress to jpeg failed: %s
    ", tjGetErrorStr());
        }
        tjDestroy(handle);
    
        return ret;
    }
    
    int main(int argc, char* argv[]) {
        printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to YUV ↓↓↓↓↓↓↓↓↓↓
    ");
        char *inJpegName3 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
        FILE *jpegFile = fopen(inJpegName3, "rb");
        
        struct stat statbuf;
        stat(inJpegName3, &statbuf);
        int fileLen=statbuf.st_size;
        printf("fileLength2: %d
    ", fileLen);
    
        uint8_t *jpegData = malloc(fileLen);
        fread(jpegData, fileLen, 1, jpegFile);
        fclose(jpegFile);
    
        uint8_t *yuvData;
        int yuvSize;
        int yuvType;
        tjpeg2yuv(jpegData, fileLen, &yuvData, &yuvSize, &yuvType);
        
        printf("size: %d; type: %d
    ", yuvSize, yuvType);
        
        char *yuvSuffix;
        if(yuvType == TJSAMP_444) {
            yuvSuffix = ".yuv444";
        } else if(yuvType == TJSAMP_422) {
            yuvSuffix = ".yuv422";
        } else if(yuvType == TJSAMP_420) {
            yuvSuffix = ".yuv420";
        } else if(yuvType == TJSAMP_GRAY) {
            yuvSuffix = ".yuv-gray";
        } else if(yuvType == TJSAMP_440) {
            yuvSuffix = ".yuv440";
        } else if(yuvType == TJSAMP_411) {
            yuvSuffix = ".yuv411";
        } else {
            printf("Unsupported type!");
            return -1;
        }
        printf("yuv samp: %s
    ", yuvSuffix);
        
        char yuvFileName[100];
        sprintf(yuvFileName, "/Users/staff/Desktop/libjpeg-turbo-test-image%s", yuvSuffix);
        FILE *yuvFile = fopen(yuvFileName, "wb");
        fwrite(yuvData, yuvSize, 1, yuvFile);
    
        free(jpegData);
        free(yuvData);
        fflush(yuvFile);
        fclose(yuvFile);
        printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to YUV ↑↑↑↑↑↑↑↑↑↑
    
    ");
    }
    

    运行上面的代码,将得到解码后的 YUV 文件 libjpeg-turbo-test-image.yuv420 :

    使用 ffplay 查看 YUV 文件:

    ffplay -f rawvideo -pixel_format yuv420p  -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.yuv420
    

    jpeg-to-yuv-example

    YUV to JPEG

    利用上一步获得的 YUV 文件,再次编码成 JPEG 文件。

    int tyuv2jpeg(unsigned char* yuv_buffer, int yuv_size, int width, int height, int subsample, unsigned char** jpeg_buffer, unsigned long* jpeg_size, int quality) {
        tjhandle handle = NULL;
        int flags = 0;
        int padding = 1; // 1或4均可,但不能是0
        int need_size = 0;
        int ret = 0;
    
        handle = tjInitCompress();
    
        flags |= 0;
    
        need_size = tjBufSizeYUV2(width, padding, height, subsample);
        if (need_size != yuv_size) {
            printf("we detect yuv size: %d, but you give: %d, check again.
    ", need_size, yuv_size);
            return 0;
        }
    
        ret = tjCompressFromYUV(handle, yuv_buffer, width, padding, height, subsample, jpeg_buffer, jpeg_size, quality, flags);
        if (ret < 0) {
            printf("compress to jpeg failed: %s
    ", tjGetErrorStr());
        }
    
        tjDestroy(handle);
    
        return ret;
    }
    
    int main(int argc, char* argv[]) {
    
        printf("↓↓↓↓↓↓↓↓↓↓ Encode YUV to JPEG ↓↓↓↓↓↓↓↓↓↓
    ");
        char *yuv420FileName = "/Users/staff/Desktop/libjpeg-turbo-test-image.yuv420";
        FILE *yuv420File = fopen(yuv420FileName, "rb");
        int yuv420Width = 800, yuv420Height = 800;
        int yuvSubsample = TJSAMP_420;
        uint8_t *yuv2jpegBuffer;
        unsigned long yuv2JpegSize;
    
        struct stat yuv420FileStat;
        stat(yuv420FileName, &yuv420FileStat);
        int yuv420FileLen = yuv420FileStat.st_size;
        printf("yuv420 file length: %d
    ", yuv420FileLen);
    
        uint8_t * yuv420Data = malloc(yuv420FileLen);
        fread(yuv420Data, yuv420FileLen, 1, yuv420File);
        printf("yuv420 read finish!
    ");
    
        tyuv2jpeg(yuv420Data, yuv420FileLen, yuv420Width, yuv420Height, yuvSubsample, &yuv2jpegBuffer, &yuv2JpegSize, 80);
        printf("jpeg data size: %ld
    ", yuv2JpegSize);
    
        FILE *yuv2JpegOutFile = fopen("/Users/staff/Desktop/libjpeg-turbo-yuv-to-jpeg.jpeg", "wb");
        fwrite(yuv2jpegBuffer, yuv2JpegSize, 1, yuv2JpegOutFile);
    
        fclose(yuv420File);
        fflush(yuv2JpegOutFile);
        fclose(yuv2JpegOutFile);
        free(yuv420Data);
    
        printf("↑↑↑↑↑↑↑↑↑↑ Encode YUV to JPEG ↑↑↑↑↑↑↑↑↑↑
    
    ");
        return 0;
    }
    

    运行上面的代码,将得到编码后的 JPEG 文件 libjpeg-turbo-yuv-to-jpeg.jpeg :

    libjpeg-turbo-yuv-to-jpeg.jpeg

    Congratulations!

    至此,我们学会了使用 libjpeg-turbo 处理 JPEG 图片。


    代码:
    16-rgb-to-jpeg-library

    参考资料:

    Main libjpeg-turbo repository

    JPEG图像压缩算法流程详解

    libjpeg学习4:libjpeg-turbo之YUV

    关于在Linux下使用TurboJPEG库

    FFmpeg & FFPlay 命令基本用法

    内容有误?联系作者:

    联系作者


  • 相关阅读:
    头文件<stdarg.h>
    头文件<signal.h>
    头文件<setjmp.h>
    头文件<math.h>
    头文件<locale.h>
    头文件<limits.h>
    头文件<ctype.h>
    头文件<assert.h>
    PHP error_reporting
    八大排序算法
  • 原文地址:https://www.cnblogs.com/binglingziyu/p/audio-video-basic-16-libjpeg-turbo-handle-jpeg.html
Copyright © 2020-2023  润新知