libjpeg 处理CMYK 格式数据以及iCC配置文件
- ICC文件,又叫ICC Color Profile,是指设备管理色彩特性的文件,各种具有色彩管理功能的软件(如photoshop)可以依据ICC文件的配置对不同设备的颜色特点进行准确地显示,转换和改变。同时,也能让颜色在不同设备上的传递过程时的失真最小 。
- ICC文件是将所有和设备有关的颜色数据(RGB和CMYK数据),一一对应到和设备无关的Lab颜色模式上。从而通过Lab模式做为传递中介,保持所有颜色外观的一致,icc文件用来描述设备的颜色特性,并用于后期的空间转换。如显示器的icc文件,记录了显示器RGB和XYZ颜色关系,打印机和印刷机的icc记录了CMYK和lab颜色关系,XYZ,lab等颜色空间是与设备无关的颜色空间,统称为PCS空间,为颜色转换提供了中间空间。可以想象,如果显示器模拟印刷机的颜色,不可能将印刷机CMYK直接转换成显示器RGB,因为CMYK是设备的输出值,RGB是显示器的输入值,那怎么产生联系?CMYK-LAB-XYZ-RGB,这样就能进行颜色的转换
- ICC profile 的配置文件表:https://www.kamilet.cn/icc-profile/
CMYK convert RGB
-
对于存储格式为CMYK的图像,通常应用于jpeg和tiff格式中,利用libjpeg和libtiff库解析图片数据时,需要根据解析的方式进行相应的转换
-
解析libjpeg时
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "libjpeg/jpeglib.h" #include "libjpeg/jerror.h" typedef struct _Mat{ unsigned int nImageWidth; unsigned int nImageHeight; unsigned int nChannels; unsigned char* pImageData; // 像素数据,RGB排列 } Mat, *pHddMat; int decodeJpgImage(const char* jpgSource, Mat* pImgMat){ struct jpeg_decompress_struct cinfo; jpeg_error_manager jerr; FILE* fp; JSAMPARRAY buffer; int row_stride = 0; unsigned char* dst = NULL; int rgb_size; fp = fopen(jpgSource, "rb"); if (fp == NULL) { printf("open input jpg file %s failed.\n", jpgSource); return -1; } cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = jpeg_error_exit; if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); fclose(fp); printf("parse %s input jpg file failed.\n", jpgSource); return -2; } jpeg_create_decompress(&cinfo); // step 2a: specify data source (eg, a handle) jpeg_stdio_src(&cinfo, fp); // step 2b: save special markers for later reading jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); int m = 0; for (m = 0; m < 16; m++) { jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); } // step 3: read handle parameters with jpeg_read_header() int retcode = jpeg_read_header(&cinfo, TRUE); int colorType = cinfo.num_components; switch (colorType) { case 1: cinfo.out_color_space = JCS_GRAYSCALE; cinfo.out_color_components = 1; break; case 4: cinfo.out_color_space = JCS_CMYK; cinfo.out_color_components = 4; break; default: cinfo.out_color_space = JCS_RGB; cinfo.out_color_components = 3; break; } jpeg_start_decompress(&cinfo); row_stride = cinfo.output_width * cinfo.output_components; rgb_size = row_stride * cinfo.output_height; // image data size buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); pImgMat->nImageWidth = cinfo.output_width; pImgMat->nImageHeight = cinfo.output_height; pImgMat->nChannels = cinfo.num_components; pImgMat->pImageData = (unsigned char*)malloc(sizeof(unsigned char) * rgb_size); // 分配总内存 dst = pImgMat->pImageData; while (cinfo.output_scanline < cinfo.output_height){ JSAMPROW src = buffer[0]; jpeg_read_scanlines(&cinfo, buffer, 1); // 解压每一行 // if input is CMYK for(unsigned x = 0; x < cinfo.output_width; x++) { // CMYK pixels are inverted dst[0] = ~src[0]; // C dst[1] = ~src[1]; // M dst[2] = ~src[2]; // Y dst[3] = ~src[3]; // K src += 4; dst += 4; } //memcpy(dst, buffer[0], row_stride); //dst += row_stride; } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); fclose(fp); return 0; } int encodeJpgImage(const char* jpgSavefile, Mat* jpgImg,int quality){ struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; int row_stride = 0; FILE* fp = NULL; JSAMPROW row_pointer[1]; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); fp = fopen(jpgSavefile, "wb"); if (fp == NULL) { printf("open output %s file failed.\n", jpgSavefile); return -1; } jpeg_stdio_dest(&cinfo, fp); cinfo.image_width = jpgImg->nImageWidth; cinfo.image_height = jpgImg->nImageHeight; cinfo.input_components = jpgImg->nChannels; int colorType = cinfo.input_components; switch (colorType) { case 1: cinfo.in_color_space = JCS_GRAYSCALE; break; case 4: cinfo.in_color_space = JCS_CMYK; break; default: cinfo.in_color_space = JCS_RGB; break; } jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); //cinfo.optimize_coding = TRUE; jpeg_start_compress(&cinfo, TRUE); row_stride = jpgImg->nImageWidth * cinfo.input_components; while (cinfo.next_scanline < cinfo.image_height){ row_pointer[0] = &jpgImg->pImageData[cinfo.next_scanline * row_stride]; BYTE *target_p = row_pointer[0]; for(unsigned x = 0; x < cinfo.image_width; x++) { // CMYK pixels are inverted target_p[0] = ~target_p[0]; // C target_p[1] = ~target_p[1]; // M target_p[2] = ~target_p[2]; // Y target_p[3] = ~target_p[3]; // K target_p += 4; } jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); fclose(fp); return 0; }
- CMYK转换RGB
int cols = pImgMat->nImageWidth; int rows = pImgMat->nImageHeight; int channels = pImgMat->nChannels; for (int row = 0; row < rows; row++) { unsigned char* imgPtr = pImgMat->pImageData + row * cols * channels; for (int j = 0; j < cols; j++) { int c = imgPtr[j*channels+0]; int m = imgPtr[j*channels+1]; int y = imgPtr[j*channels+2]; int k = imgPtr[j*channels+3]; unsigned r = (255 - c) * (255 - k) >> 8; unsigned g = (255 - m) * (255 - k) >> 8; unsigned b = (255 - y) * (255 - k) >> 8; //if read value not ~255 ops, similar to opencv library //unsigned r = k - ((255 - c)*k >> 8); //unsigned g = k - ((255 - m)*k >> 8); //unsigned b = k - ((255 - y)*k >> 8); } }
RGB to CMYK conversion formula
The R,G,B values are divided by 255 to change the range from 0…255 to 0…1:
R’ = R/255
G’ = G/255
B’ = B/255
The black key (K) color is calculated from the red (R’), green (G’) and blue (B’) colors:
K = 1-max(R’, G’, B’)
The cyan color © is calculated from the red (R’) and black (K) colors:
C = (1-R’-K) / (1-K)
The magenta color (M) is calculated from the green (G’) and black (K) colors:
M = (1-G’-K) / (1-K)
The yellow color (Y) is calculated from the blue (B’) and black (K) colors:
Y = (1-B’-K) / (1-K)