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。