Linux:JPEG編解碼,PNG解碼,JPEG疊加PNG水印

JPEG圖片疊加PNG圖片水印

本文的主要目的是將一張JPEG的圖片疊加一張PNG格式的水印,很多基礎理論知識本文並沒有涉及,博主也不是很懂。但是JPEG的編解碼,PNG的解碼大家還是可以參照完成的。本文的代碼上傳到了CSDN,基礎好的同學可以直接下載使用就好了,積分有點多,要5積分,手快了沒有看到在哪裏設置,上傳到這裏主要是想搞點積分。
https://download.csdn.net/download/qq_31878855/11160413
實在沒有積分的可以私聊我。

一、JEPG圖片解碼

JPEG圖像解碼需要用到開源的解碼庫,IJG是一個非正式的組,它爲JPEG圖像壓縮編寫和發佈一個廣泛使用的免費庫。目前最新的版本是2018年1月14日發佈的9c版本。Linux下載地址
http://www.ijg.org/files/jpegsrc.v9c.tar.gz
本文所使用的是8c版本,
http://www.ijg.org/files/jpegsrc.v8c.tar.gz

1.解壓

tar -zxvf jpegsrc.v8c.tar.gz

2.編譯JPEG庫

cd jpeg-8c/ #cd到解壓目錄
mkdir tmp #創建一個臨時文件夾,用於存放安裝文件
./configure --prefix=$(pwd)/tmp #配置並生成Makefile
make && make install #編譯並安裝
生成文件如下圖所示:
在這裏插入圖片描述

3.編寫解碼JPEG圖片代碼。

①函數介紹
//寫輸出bmp文件的頭部分,文件信息由庫中解碼函數提供
void write_bmp_header(j_decompress_ptr cinfo, FILE *output_file)
{
        char bmpfileheader[14];
        char bmpinfoheader[40];
        long headersize, bfSize;
        int bits_per_pixel, cmap_entries;
        int step;
        /* Compute colormap size and total file size */
        if (cinfo->out_color_space == JCS_RGB) {
                if (cinfo->quantize_colors) {
                        /* Colormapped RGB */
                        bits_per_pixel = 8;
                        cmap_entries = 256;
                } else {
                        /* Unquantized, full color RGB */
                        bits_per_pixel = 24;
                        cmap_entries = 0;
                }
        } else {
                /* Grayscale output.  We need to fake a 256-entry colormap. */
                bits_per_pixel = 8;
                cmap_entries = 256;
        }
        step = cinfo->output_width * cinfo->output_components;
        while ((step & 3) != 0) step++;
        /* File size */
        headersize = 14 + 40 + cmap_entries * 4; /* Header and colormap */

        bfSize = headersize + (long) step * (long) cinfo->output_height;

        /* Set unused fields of header to 0 */
        memset(bmpfileheader, 0, sizeof(bmpfileheader));
        memset(bmpinfoheader, 0 ,sizeof(bmpinfoheader));

        /* Fill the file header */
        bmpfileheader[0] = 0x42;/* first 2 bytes are ASCII 'B', 'M' */
        bmpfileheader[1] = 0x4D;
        PUT_4B(bmpfileheader, 2, bfSize); /* bfSize */
        /* we leave bfReserved1 & bfReserved2 = 0 */
        PUT_4B(bmpfileheader, 10, headersize); /* bfOffBits */

        /* Fill the info header (Microsoft calls this a BITMAPINFOHEADER) */
        PUT_2B(bmpinfoheader, 0, 40);   /* biSize */
        PUT_4B(bmpinfoheader, 4, cinfo->output_width); /* biWidth */
        PUT_4B(bmpinfoheader, 8, cinfo->output_height); /* biHeight */
        PUT_2B(bmpinfoheader, 12, 1);   /* biPlanes - must be 1 */
        PUT_2B(bmpinfoheader, 14, bits_per_pixel); /* biBitCount */
        /* we leave biCompression = 0, for none */
        /* we leave biSizeImage = 0; this is correct for uncompressed data */
        if (cinfo->density_unit == 2) { /* if have density in dots/cm, then */
                PUT_4B(bmpinfoheader, 26, (INT32) (cinfo->X_density*100)); /* XPels/M */
                PUT_4B(bmpinfoheader, 30, (INT32) (cinfo->Y_density*100)); /* XPels/M */
        }
        PUT_2B(bmpinfoheader, 32, cmap_entries); /* biClrUsed */
        /* we leave biClrImportant = 0 */
        if (fwrite(bmpfileheader, 1, 14, output_file) != (size_t) 14) {
                printf("write bmpfileheader error\n");
        }
        if (fwrite(bmpinfoheader, 1, 40, output_file) != (size_t) 40) {
                printf("write bmpinfoheader error\n");
        }
        if (cmap_entries > 0) {
        }
}
// 寫入bmp圖像 rgb 數據
void write_pixel_data(j_decompress_ptr cinfo, unsigned char *output_buffer, FILE *output_file)
{
        int rows, cols;
        int row_width;
        int step;
        
        int x=20,y=cinfo->output_height -watermask_info.height -20 ;
        unsigned char *tmp = NULL;
unsigned char *pdata;

        row_width = cinfo->output_width * cinfo->output_components;
        step = row_width;
        while ((step & 3) != 0) step++;

        pdata = (unsigned char *)malloc(step);
        memset(pdata, 0, step);
				printf("cinfo->output_components=%d,\n",cinfo->output_components);
        tmp = output_buffer + row_width * (cinfo->output_height - 1);
        
      
        int i;
        
        for (rows = 0; rows < cinfo->output_height; rows++) {
                for (cols = 0; cols < row_width; cols += 3) {
                        pdata[cols + 2] = tmp[cols + 0];
                        pdata[cols + 1] = tmp[cols + 1];
                        pdata[cols + 0] = tmp[cols + 2];
                  //       pdata[cols + 0] = tmp[cols + 0];
                   //     pdata[cols + 1] = tmp[cols + 1];
                   //     pdata[cols + 2] = tmp[cols + 2];
                }
                tmp -= row_width;
 #if 0     // 這裏是疊加一個bmp圖片水印,bmp的圖片不是透明的疊加出來效果不好 //watermask_info 就是水印的數據
                if( rows >= y  &&  rows <y+watermask_info.height  ){
                	for( i=0;i<watermask_info.width*3;i++)	
                	{
                		pdata[ x*3+i]  = watermask_info.data[i+(rows - y )*watermask_info.width*3];
                	}
                }
#endif                
                fwrite(pdata, 1, step, output_file);
        }
				free(watermask_info.data);
        free(pdata);
}
// 解碼函數
int decode_jpeg_file(const char *input_filename, const char *output_filename)
{
        struct jpeg_decompress_struct cinfo; //jpeg使用的對象結構體
        struct jpeg_error_mgr jerr;       // jpeg錯誤處理結構體
        FILE *input_file;
        FILE *output_file;
        JSAMPARRAY buffer;//IJG還定義了JSAMPROW和JSAMPARRAY,分別表示一行JSAMPLE和一個2D的JSAMPLE數組
        int row_width;

        unsigned char *output_buffer;
        unsigned char *tmp = NULL;

        cinfo.err = jpeg_std_error(&jerr); //將錯誤結構體綁定到jpeg對象上
// 輸入文件 需要解碼的jpg圖片
        if ((input_file = fopen(input_filename, "rb")) == NULL) {
                fprintf(stderr, "can't open %s\n", input_filename);
                return -1;
        }
//輸出文件,解碼後的bmp文件
        if ((output_file = fopen(output_filename, "wb")) == NULL) {
                fprintf(stderr, "can't open %s\n", output_filename);
                return -1;
        }

        jpeg_create_decompress(&cinfo);  //初始化jpeg 對象

        /* Specify data source for decompression */
        jpeg_stdio_src(&cinfo, input_file); //利用標準C中的文件指針傳遞要打開的jpg文件
        /* Read file header, set default decompression parameters */
        (void) jpeg_read_header(&cinfo, TRUE); //IJG將圖像的缺省信息填充到cinfo結構中以便程序使用。

        /* Start decompressor */
        (void) jpeg_start_decompress(&cinfo);

        row_width = cinfo.output_width * cinfo.output_components;//每個像素中的顏色通道數cinfo.output_components(比如灰度爲1,全綵色爲3)等。

/*JPOOL_IMAGE表示分配的內存空間將在調用jpeg_finish_compress,jpeg_finish_decompress,jpeg_abort後被釋放,而如果此參數改爲JPOOL_PERMANENT則表示內存將一直到JPEG對象被銷燬時才被釋放。row_stride如上所說,是每行數據的實際大小。最後一個參數是要分配多少行數據。此處只分配了一行。*/
        buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_width, 1);

        write_bmp_header(&cinfo, output_file); //填充bmp 的頭,輸出的bmp文件

        output_buffer = (unsigned char *)malloc(row_width * cinfo.output_height);
        memset(output_buffer, 0, row_width * cinfo.output_height);
        tmp = output_buffer;
				/*output_scanline表示當前已經讀取的行數,如此即可依次讀出圖像的所有數據,並填充到緩衝區中,參數1表示的是每次讀取的行數。*/
        /* Process data */
        while (cinfo.output_scanline < cinfo.output_height) {
                (void) jpeg_read_scanlines(&cinfo, buffer, 1);// 讀出解碼後的數據

                memcpy(tmp, *buffer, row_width);// 保存到輸出buff中
                tmp += row_width;
        }
        write_pixel_data(&cinfo, output_buffer, output_file); //寫入bmp中
        free(output_buffer);
        (void) jpeg_finish_decompress(&cinfo); //解壓縮完畢
        jpeg_destroy_decompress(&cinfo);//釋放資源
        /* Close files, if we opened them */
        fclose(input_file);
        fclose(output_file);
        return 0;
}
//工具函數,宏
#define PUT_2B(array,offset,value)  \
        (array[offset] = (char) ((value) & 0xFF), \
         array[offset+1] = (char) (((value) >> 8) & 0xFF))
#define PUT_4B(array,offset,value)  \
        (array[offset] = (char) ((value) & 0xFF), \
         array[offset+1] = (char) (((value) >> 8) & 0xFF), \
         array[offset+2] = (char) (((value) >> 16) & 0xFF), \
         array[offset+3] = (char) (((value) >> 24) & 0xFF))
int get_2b(unsigned char*a,int offset)
{
    return a[offset+1]<<8|a[offset];
}
int get_4b(unsigned char*a,int offset)
{
    return (a[offset+3]<<24)|(a[offset+2]<<16)|(a[offset+1]<<8)|a[offset];
} 
②調用上面的函數

int decode_jpeg_file(const char *input_filename, const char *output_filename);
input_filename // 爲需要解碼的圖像文件名
output_filename //輸出解碼後bmp文件的文件名,會自動創建

③編譯

gcc jpeg_decode.c -l jpeg -L jpeg-8c/tmp/lib/ -o jpeg_decode.app

4.成功解碼

沒有運行之前
在這裏插入圖片描述
在這裏插入圖片描述
運行./jpeg_decode.app,之後生成tt.bmp文件。
在這裏插入圖片描述
打開tt.bmp
在這裏插入圖片描述
成功的將jpeg圖片解碼成了bmp圖片。
大小對比:
在這裏插入圖片描述
解碼成功後的BMP文件比JPEG格式的文件大了許多。

二、JEPG圖片編碼

現在找一張bmp圖片,把它編碼成jpeg文件格式。

1.代碼編寫

①解析原始的bmp圖片,獲取圖像高度,寬度等信息。
void read_bmp_header(char *bmpfilename)
{
    unsigned char bmpfileheader[14];//¿¿¿
    unsigned char bmpinfoheader[40];//¿¿¿
    bmpfile=fopen(bmpfilename,"r");//
    if(bmpfile<0)
    printf("open bmp file error!\n");
    printf("open bmp file success!\n");
    fread(bmpfileheader,14,1,bmpfile);
    int type=get_2b(bmpfileheader,0);
    printf("type=0x%x\n",type);
    int filesize=get_4b(bmpfileheader,2);
    printf("filesize=%d bytes\n",filesize);
    headersize=get_4b(bmpfileheader,10);
    printf("headersize=%d bytes\n",headersize);
    if(headersize>54)
        printf("colormap size=%d bytes\n",headersize-54);
    fseek(bmpfile,14,SEEK_SET);
    fread(bmpinfoheader,40,1,bmpfile);
    image_width=get_4b(bmpinfoheader,4);
    while (image_width%4!=0)
        image_width++;
    printf("weight=%d\n",image_width);
    image_height=get_4b(bmpinfoheader,8);
    printf("height=%d\n",image_height);
    bits_per_pixel=get_2b(bmpinfoheader,14);
    printf("bits_per_pixel=%d\n",bits_per_pixel);
    depth=bits_per_pixel/8;
    image_size=image_width*image_height*depth;
    src_data=(unsigned char *)malloc(image_size);
    fseek(bmpfile,headersize,SEEK_SET);
    fread(src_data,sizeof(unsigned char)*image_size,1,bmpfile);
    fclose(bmpfile);
}
② 編碼JEPG圖片,將bmp中的rgb原始數據編碼成jpeg格式。
void encode_jpeg_file (char * outfilename, unsigned char * buffer,int quality)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * outfile;
    unsigned char *dst_data;
    int i,j;
    //char *point;
    JSAMPROW  row_pointer[1];
    //js amparray buffer;
    int row_stride;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);
    if ((outfile = fopen(outfilename, "wb")) == NULL) {
    fprintf(stderr, "can't open %s\n", outfilename);
    exit(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 =  depth;        /* # of color components per pixel */
   // cinfo.in_color_space = (depth==3) ? jcs_rgb : jcs_grayscale;     /* colorspace of input image */
   cinfo.in_color_space = (depth==3) ? JCS_RGB : JCS_GRAYSCALE; 
   	printf("in_color_space = %d ,input_components =%d JCS_RGB= %d \n",cinfo.in_color_space, cinfo.input_components,JCS_RGB);
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-jpeg values */);
    dst_data=(unsigned char *)malloc(image_size*sizeof(unsigned char));
    //bgr->rgb
    #if 1
    for(i=0;i<image_height;i++){
        for(j=0;j<image_width;j++)
        {
            if(depth==1)//¿¿¿
                *(dst_data+i*image_width+j)=*(src_data+i*image_width+j);
            else //¿¿¿
                {
            *(dst_data+i*image_width*depth+j*3+0)=*(src_data+i*image_width*depth+j*3+2);
                    *(dst_data+i*image_width*depth+j*3+1)=*(src_data+i*image_width*depth+j*3+1);
                   *(dst_data+i*image_width*depth+j*3+2)=*(src_data+i*image_width*depth+j*3+0);
                   // dst_data[j + 2] = src_data[j + 0];
                   // dst_data[j + 1] = src_data[j + 1];
                    //dst_data[j + 0] = src_data[j + 2];
                }
        }
    }
    #endif
    //dst_data=src_data;
    jpeg_start_compress(&cinfo, TRUE);

    row_stride = image_width * cinfo.input_components;    /* js amples per row in image_buffer */
    
    while (cinfo.next_scanline < cinfo.image_height) {
         row_pointer[0] = & dst_data[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];//cinfo.next_scanline * row_stride
        // row_pointer[0] = & dst_data[cinfo.next_scanline  * row_stride];
         (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);

    }

    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
    free(src_data);
    free(dst_data);
}
③ 調用上訴函數

先調用 read_bmp_header()獲取需要編碼的bmp圖片信息,在調用encode_jpeg_file函數編碼jpeg數據並保存爲jeg格式的圖片。

2.編譯

gcc jpeg_encode.c -l jpeg -L jpeg-8c/tmp/lib/ -o jpeg_encode.app

3.成功編碼

沒運行之前文件如下:
在這裏插入圖片描述

運行app(如果出現找不到共享庫的錯誤,參照下PNG那節,導出共享庫路徑)
在這裏插入圖片描述
打開文件對比,
在這裏插入圖片描述在這裏插入圖片描述
可以發現上面兩張照片效果無明顯差別但是文件大小確小了很多。

三、PNG解碼

水印文件一般爲PNG格式的文件,32bit帶透明數據。想要疊加水印還的解碼PNG格式的圖片。

1.下載庫

解碼PNG格式的文件需要用到libpng庫,網上搜索
libpng download 。
又升級了libpng-1.6.36.tar.xz ,下載地址 https://downloads.sourceforge.net/libpng/libpng-1.6.36.tar.xz。
不過我用的是1.6.34版本。

2.解壓並編譯

tar -zxvf libpng-1.6.34.tar.gz
cd libpng-1.6.34
mkdir tmp
./configure --prefix=$(pwd)/tmp
make && make install
交叉編譯運行configure 時指定下編譯器,如:./configure --prefix=$(pwd)/tmp --host=arm-linux 也可以使用CC變量。
生成目錄如下:
在這裏插入圖片描述

3.編寫代碼

① 解碼函數

偷了個懶沒有保存爲bmp文件,直接保存爲rgb的裸數據流文件,後面圖片疊加的時候也不用解析bmp文件了。水印文件也不用經常換,後面直接放到h文件了。

char*  decode_png(char* name)
{
 int i, j;
 int m_width, m_height;
 png_infop info_ptr;             //圖片信息的結構體
 png_structp png_ptr;         //初始化結構體,初始生成,調用api時注意傳入

 FILE* file = fopen(name, "rb");    //打開的文件名
 printf("%s, %d\n", __FUNCTION__, __LINE__);

 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);   //創建初始化libpng庫結構體
  info_ptr = png_create_info_struct(png_ptr);                                                 //創建圖片信息結構體
 setjmp(png_jmpbuf(png_ptr));                              //設置錯誤的返回點
 // 這句很重要
 png_init_io(png_ptr, file);         //把文件加載到libpng庫結構體中
 // 讀文件了
 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, 0);        //讀文件內容到info_ptr中
 // 得到文件的寬高色深
 if ((png_ptr != NULL) && (info_ptr != NULL))
 {
  m_width = png_get_image_width(png_ptr, info_ptr);             
  m_height = png_get_image_height(png_ptr, info_ptr);                          //通過png庫中的api獲取圖片的寬度和高度
  printf("%s, %d, m_width =%d, m_height = %d\n", __FUNCTION__, __LINE__, m_width, m_height);
 }
 int color_type = png_get_color_type(png_ptr, info_ptr);                          //通過api獲取color_type
 printf("%s, %d, color_type = %d\n", __FUNCTION__, __LINE__, color_type);


 int size = m_height * m_width * 4;
 unsigned char *bgra = NULL;
 bgra = malloc(size);
 if (NULL == bgra)
 {
  printf("%s, %d, bgra == NULL\n", __FUNCTION__, __LINE__);
  return;
 }
 int pos = 0;
 // row_pointers裏邊就是傳說中的rgb數據了
 png_bytep* row_pointers = png_get_rows(png_ptr, info_ptr);
 // 拷貝!!注意,如果你讀取的png沒有A通道,就要3位3位的讀。還有就是注意字節對其的問題,最簡單的就是別用不能被4整除的寬度就行了。讀過你實在想用,就要在這裏加上相關的對齊處理。
 for(i = 0; i < m_height; i++)
 {
    for(j = 0; j < (4 * m_width); j += 4)
    {
     bgra[pos++] = row_pointers[i][j + 2]; // blue
     bgra[pos++] = row_pointers[i][j + 1]; // green
     bgra[pos++] = row_pointers[i][j];   // red
     bgra[pos++] = row_pointers[i][j + 3]; // alpha
    }
 }
 // 好了,你可以用這個數據作任何的事情了。。。把它顯示出來或者打印出來都行。
/* for (i = 0; i < size; i++ )
 {
  printf("%s, %d, bgra[%d] = %d\n", __FUNCTION__, __LINE__,  i, bgra[i]);
 }
 */
 char tmp[10]={0};
//保存rgb裸流文件咯,省時省力
 FILE * f = fopen("savepng.bin","wb");
 for (i = 0; i < size; i++ )
 {
 // printf("%s, %d, bgra[%d] = %d\n", __FUNCTION__, __LINE__,  i, bgra[i]);
  	sprintf(tmp,"0x%x,",bgra[i]);
  	fwrite(tmp,strlen(tmp),1,f);
  	
 }
 	fwrite(bgra,size,1,f);
 	fclose(f);
 png_destroy_read_struct(&png_ptr, &info_ptr, 0);
 fclose(file);
 return   bgra;
}
② 編譯

gcc png_decode.c -l png -L libpng-1.6.34/tmp/lib/ -o png_decode.app

4.成功解碼

運行第一次出錯
在這裏插入圖片描述
找不到共享庫。
導出庫路徑,具體路徑看實際的。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/work/ibpng-1.6.34/tmp/lib

再次運行,成功,分辨率信息沒有保存,但是後續還是要用到。
在這裏插入圖片描述
解碼的文件名爲LOGO.png.
圖中出現警告 libpng warning: iCCP: known incorrect sRGB profile可無需體會,如果覺得礙眼可以百度解決下,當前不影響我們疊加水印。
生成文件
在這裏插入圖片描述
至於解碼出來的數據對不對還的看下節了。

四、疊加水印

疊加水印首先我們將JPEG格式的文件解碼成RGB原始數據,再將上面解碼出來的PNG圖片的RGBA數據取出,然後將兩種數據按比例混合。由於JPEG是有損壓縮,會產生迭代有損,在重複壓縮和解碼的過程中會不斷丟失信息使圖像質量下降。

1、編寫代碼

因爲後面需要移植到嵌入式平臺上,爲了方便使用,我將PNG的數據保存到H文件中,建立了一個常量數組來保存PNG數據,便於使用。當然這種方法不適合通用,將PNG解碼和JPEG編解碼結合起來實在點,不過到嵌入式設備了就得連帶將PNG和JPEG的庫文件都移植進去空間會變大,可以根據情況選取。

①填充PNG數據
void fill_png_info()
{
	watermask_info.width=138;
	watermask_info.height=45;
	watermask_info.depth=4;
	watermask_info.image_size=watermask_info.width * watermask_info.height *watermask_info.depth ;
	watermask_info.bits_per_pixel=watermask_info.depth * 8;
	watermask_info.data=(char *)png_watermark_data;
}

這就是自己寫的一個結構體,保存一些後面要用的信息,png_watermark_data這是個const char 類型的數字放的就是PNG解碼出來的純RBGA數據流,後面疊加的時候需要用到。

②疊加水印函數

整體思想就是先按上文的方式解碼JPEG文件,再將PNG解碼後的數據疊加到JPEG解碼數據上,整合後按JPEG格式編碼。

int read_jpeg_file(const char *input_filename, const char *output_filename)
{
        struct jpeg_decompress_struct cinfo; //jpeg使用的對象結構體
        struct jpeg_error_mgr jerr;       // jpeg錯誤處理結構體
        FILE *input_file;
        FILE *output_file;
        JSAMPARRAY buffer;//IJG還定義了JSAMPROW和JSAMPARRAY,分別表示一行JSAMPLE和一個2D的JSAMPLE數組
        int row_width;

        unsigned char *output_buffer;
        unsigned char *tmp = NULL;
        cinfo.err = jpeg_std_error(&jerr); //將錯誤結構體綁定到jpeg對象上
        if ((input_file = fopen(input_filename, "rb")) == NULL) {
                fprintf(stderr, "can't open %s\n", input_filename);
                return -1;
        }
        jpeg_create_decompress(&cinfo);  //初始化jpeg 對象
        /* Specify data source for decompression */
        jpeg_stdio_src(&cinfo, input_file); //利用標準C中的文件指針傳遞要打開的jpg文件
        /* Read file header, set default decompression parameters */
        (void) jpeg_read_header(&cinfo, TRUE); //IJG將圖像的缺省信息填充到cinfo結構中以便程序使用。
        /* Start decompressor */
        (void) jpeg_start_decompress(&cinfo);
        row_width = cinfo.output_width * cinfo.output_components;//每個像素中的顏色通道數cinfo.output_components(比如灰度爲1,全綵色爲3)等。
        buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_width, 1);

        output_buffer = (unsigned char *)malloc(row_width * cinfo.output_height);
        memset(output_buffer, 0, row_width * cinfo.output_height);
        tmp = output_buffer;
				/*output_scanline表示當前已經讀取的行數,如此即可依次讀出圖像的所有數據,並填充到緩衝區中,參數1表示的是每次讀取的行數。*/
        /* Process data */
        while (cinfo.output_scanline < cinfo.output_height) {
                (void) jpeg_read_scanlines(&cinfo, buffer, 1);

                memcpy(tmp, *buffer, row_width);
                tmp += row_width;
        }
		if(	png_add_watermark(&cinfo, output_buffer,10,10) <0)
		{
			printf("add watermark failed \n");
			return -1;
		}

	jpeg_finish_decompress(&cinfo); //解壓縮完畢
	encode_jpeg(&cinfo,output_filename, output_buffer,80);
		
	jpeg_destroy_decompress(&cinfo);//釋放資源
	free(output_buffer);
	/* Close files, if we opened them */
	fclose(input_file);
	return 0;
}
②整合數據函數

PNG格式解碼後的RGBA數據當中的A數據包含着該像素點在圖像當中的比列,我們根據這個比列進行融合。融合在for循環中,最關鍵的是要理解像素點數據在數組當中的位置進行偏移融合,JPEG解碼的是BGR數據,然PNG數組當中的數據我在解碼的時候爲了方便就按BRGA的順序存放,所以就無需進行數據順序的交換,按實際順序即可。JPEG解碼後的數據是按三個字節一像素偏移,PNG數據是按四字節一像素偏移。

int	png_add_watermark(struct jpeg_decompress_struct *cinfo, unsigned char *output_buffer,int x ,int y)
{
    int rows, cols ,i,j;
    int row_width;
    int step;
    unsigned char *tmp = NULL;
    if( (x+watermask_info.width  )  > cinfo->output_width || (y+watermask_info.height)>cinfo->output_height )
    	return -1;
	row_width = watermask_info.width*watermask_info.depth;
/*rgb - bgr*/
	step = cinfo->output_width * cinfo->output_components;
	//printf(   " output_width =%d,output_components=%d, output_height  =%d \n",cinfo->output_width,cinfo->output_components,cinfo->output_height  );
	//printf("output_buffer = %p  \n",output_buffer);
	tmp = output_buffer + (step) * (y); // 得到當前的偏移行地址
	//char * wm_src_data = dst_data + ( watermask_info.height-1  )* row_width;
	int cnt;
	int num;
	  for(rows = 0 ; rows <watermask_info.height ; rows++ )
	  {
		  for(cols = 0,cnt=0 ; cols <watermask_info.width *(watermask_info.depth ) ; cols+=4 )
		  {
		  		num = watermask_info.data[ cols+3  +  rows* row_width];
		  		//output_buffer[(y*row_width)+rows] = wm_src_data[   ]
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100  + watermask_info.data[ cols+0  +  rows* row_width] *(num) /100 ;  
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100 + watermask_info.data[ cols+1  +  rows* row_width] *(num) / 100;  
				tmp[ cinfo->output_components*x+cnt] = tmp[ cinfo->output_components*x+cnt++]*(100-num)/100  + watermask_info.data[ cols+2  +  rows* row_width] *(num ) / 100;  
		  }
		  tmp+=step; //移到下一行
	  }
	//  free(watermask_info.data);
	  return 0;
}

2、編譯代碼

gcc JpegAddWatermark.c -l jpeg -L jpeg-8c/tmp/lib/ -o AddWm.app

3、運行結果

成功的將水印疊加了上去。
在這裏插入圖片描述

結束語

至此本文就編寫完成,該項目已經完成快一年半了,很多細節知識都忘記了,當中如果有什麼錯誤還望各位能夠在評論區指正,有什麼不明白的地方也歡迎在評論區中討論,能給個贊最好了/wx。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章