// ========================================================== // ICO Loader and Writer // // Design and implementation by // - Floris van den Berg (flvdberg@wxs.nl) // - Hervé Drolon (drolon@infonie.fr) // // This file is part of FreeImage 3 // // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER // THIS DISCLAIMER. // // Use at your own risk! // ========================================================== #include "FreeImage.h" #include "Utilities.h" // ---------------------------------------------------------- // Constants + headers // ---------------------------------------------------------- #ifdef _WIN32 #pragma pack(push, 1) #else #pragma pack(1) #endif // These next two structs represent how the icon information is stored // in an ICO file. typedef struct tagICONHEADER { WORD idReserved; // reserved WORD idType; // resource type (1 for icons) WORD idCount; // how many images? } ICONHEADER; typedef struct tagICONDIRECTORYENTRY { BYTE bWidth; // width of the image BYTE bHeight; // height of the image (times 2) BYTE bColorCount; // number of colors in image (0 if >=8bpp) BYTE bReserved; // reserved WORD wPlanes; // color Planes WORD wBitCount; // bits per pixel DWORD dwBytesInRes; // how many bytes in this resource? DWORD dwImageOffset; // where in the file is this image } ICONDIRENTRY; #ifdef _WIN32 #pragma pack(pop) #else #pragma pack() #endif // ========================================================== // Static helpers // ========================================================== /** How wide, in bytes, would this many bits be, DWORD aligned ? */ static int WidthBytes(int bits) { return ((((bits) + 31)>>5)<<2); } /** Calculates the size of a single icon image @return Returns the size for that image */ static DWORD CalculateImageSize(FIBITMAP* icon_dib) { DWORD dwNumBytes = 0; unsigned colors = FreeImage_GetColorsUsed(icon_dib); unsigned width = FreeImage_GetWidth(icon_dib); unsigned height = FreeImage_GetHeight(icon_dib); unsigned pitch = FreeImage_GetPitch(icon_dib); dwNumBytes = sizeof( BITMAPINFOHEADER ); // header dwNumBytes += colors * sizeof(RGBQUAD); // palette dwNumBytes += height * pitch; // XOR mask dwNumBytes += height * WidthBytes(width); // AND mask return dwNumBytes; } /** Calculates the file offset for an icon image @return Returns the file offset for that image */ static DWORD CalculateImageOffset(std::vector& vPages, int nIndex ) { DWORD dwSize; // calculate the ICO header size dwSize = sizeof(ICONHEADER); // add the ICONDIRENTRY's dwSize += (DWORD)( vPages.size() * sizeof(ICONDIRENTRY) ); // add the sizes of the previous images for(int k = 0; k < nIndex; k++) { FIBITMAP *icon_dib = (FIBITMAP*)vPages[k]; dwSize += CalculateImageSize(icon_dib); } return dwSize; } /** Vista icon support @return Returns TRUE if the bitmap data is stored in PNG format */ static BOOL IsPNG(FreeImageIO *io, fi_handle handle) { BYTE png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; BYTE signature[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; long tell = io->tell_proc(handle); io->read_proc(&signature, 1, 8, handle); BOOL bIsPNG = (memcmp(png_signature, signature, 8) == 0); io->seek_proc(handle, tell, SEEK_SET); return bIsPNG; } #ifdef FREEIMAGE_BIGENDIAN static void SwapInfoHeader(BITMAPINFOHEADER *header) { SwapLong(&header->biSize); SwapLong((DWORD *)&header->biWidth); SwapLong((DWORD *)&header->biHeight); SwapShort(&header->biPlanes); SwapShort(&header->biBitCount); SwapLong(&header->biCompression); SwapLong(&header->biSizeImage); SwapLong((DWORD *)&header->biXPelsPerMeter); SwapLong((DWORD *)&header->biYPelsPerMeter); SwapLong(&header->biClrUsed); SwapLong(&header->biClrImportant); } static void SwapIconHeader(ICONHEADER *header) { SwapShort(&header->idReserved); SwapShort(&header->idType); SwapShort(&header->idCount); } static void SwapIconDirEntries(ICONDIRENTRY *ent, int num) { while(num) { SwapShort(&ent->wPlanes); SwapShort(&ent->wBitCount); SwapLong(&ent->dwBytesInRes); SwapLong(&ent->dwImageOffset); num--; ent++; } } #endif // ========================================================== // Plugin Interface // ========================================================== static int s_format_id; // ========================================================== // Plugin Implementation // ========================================================== static const char * DLL_CALLCONV Format() { return "ICO"; } static const char * DLL_CALLCONV Description() { return "Windows Icon"; } static const char * DLL_CALLCONV Extension() { return "ico"; } static const char * DLL_CALLCONV RegExpr() { return NULL; } static const char * DLL_CALLCONV MimeType() { return "image/vnd.microsoft.icon"; } static BOOL DLL_CALLCONV Validate(FreeImageIO *io, fi_handle handle) { ICONHEADER icon_header; io->read_proc(&icon_header, sizeof(ICONHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapIconHeader(&icon_header); #endif return ((icon_header.idReserved == 0) && (icon_header.idType == 1) && (icon_header.idCount > 0)); } static BOOL DLL_CALLCONV SupportsExportDepth(int depth) { return ( (depth == 1) || (depth == 4) || (depth == 8) || (depth == 16) || (depth == 24) || (depth == 32) ); } static BOOL DLL_CALLCONV SupportsExportType(FREE_IMAGE_TYPE type) { return (type == FIT_BITMAP) ? TRUE : FALSE; } static BOOL DLL_CALLCONV SupportsNoPixels() { return TRUE; } // ---------------------------------------------------------- static void * DLL_CALLCONV Open(FreeImageIO *io, fi_handle handle, BOOL read) { // Allocate memory for the header structure ICONHEADER *lpIH = (ICONHEADER*)malloc(sizeof(ICONHEADER)); if(lpIH == NULL) { return NULL; } if (read) { // Read in the header io->read_proc(lpIH, 1, sizeof(ICONHEADER), handle); #ifdef FREEIMAGE_BIGENDIAN SwapIconHeader(lpIH); #endif if(!(lpIH->idReserved == 0) || !(lpIH->idType == 1)) { // Not an ICO file free(lpIH); return NULL; } } else { // Fill the header lpIH->idReserved = 0; lpIH->idType = 1; lpIH->idCount = 0; } return lpIH; } static void DLL_CALLCONV Close(FreeImageIO *io, fi_handle handle, void *data) { // free the header structure ICONHEADER *lpIH = (ICONHEADER*)data; free(lpIH); } // ---------------------------------------------------------- static int DLL_CALLCONV PageCount(FreeImageIO *io, fi_handle handle, void *data) { ICONHEADER *lpIH = (ICONHEADER*)data; if(lpIH) { return lpIH->idCount; } return 1; } // ---------------------------------------------------------- static FIBITMAP* LoadStandardIcon(FreeImageIO *io, fi_handle handle, int flags, BOOL header_only) { FIBITMAP *dib = NULL; // load the BITMAPINFOHEADER BITMAPINFOHEADER bmih; io->read_proc(&bmih, sizeof(BITMAPINFOHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapInfoHeader(&bmih); #endif // allocate the bitmap int width = bmih.biWidth; int height = bmih.biHeight / 2; // height == xor + and mask unsigned bit_count = bmih.biBitCount; unsigned line = CalculateLine(width, bit_count); unsigned pitch = CalculatePitch(line); // allocate memory for one icon dib = FreeImage_AllocateHeader(header_only, width, height, bit_count); if (dib == NULL) { return NULL; } if( bmih.biBitCount <= 8 ) { // read the palette data io->read_proc(FreeImage_GetPalette(dib), CalculateUsedPaletteEntries(bit_count) * sizeof(RGBQUAD), 1, handle); #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB RGBQUAD *pal = FreeImage_GetPalette(dib); for(unsigned i = 0; i < CalculateUsedPaletteEntries(bit_count); i++) { INPLACESWAP(pal[i].rgbRed, pal[i].rgbBlue); } #endif } if(header_only) { // header only mode return dib; } // read the icon io->read_proc(FreeImage_GetBits(dib), height * pitch, 1, handle); #ifdef FREEIMAGE_BIGENDIAN if (bit_count == 16) { for(int y = 0; y < height; y++) { WORD *pixel = (WORD *)FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { SwapShort(pixel); pixel++; } } } #endif #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB if (bit_count == 24 || bit_count == 32) { for(int y = 0; y < height; y++) { BYTE *pixel = FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { INPLACESWAP(pixel[0], pixel[2]); pixel += (bit_count>>3); } } } #endif // bitmap has been loaded successfully! // convert to 32bpp and generate an alpha channel // apply the AND mask only if the image is not 32 bpp if(((flags & ICO_MAKEALPHA) == ICO_MAKEALPHA) && (bit_count < 32)) { FIBITMAP *dib32 = FreeImage_ConvertTo32Bits(dib); FreeImage_Unload(dib); if (dib32 == NULL) { return NULL; } int width_and = WidthBytes(width); BYTE *line_and = (BYTE *)malloc(width_and); if( line_and == NULL ) { FreeImage_Unload(dib32); return NULL; } //loop through each line of the AND-mask generating the alpha channel, invert XOR-mask for(int y = 0; y < height; y++) { RGBQUAD *quad = (RGBQUAD *)FreeImage_GetScanLine(dib32, y); io->read_proc(line_and, width_and, 1, handle); for(int x = 0; x < width; x++) { quad->rgbReserved = (line_and[x>>3] & (0x80 >> (x & 0x07))) != 0 ? 0 : 0xFF; if( quad->rgbReserved == 0 ) { quad->rgbBlue ^= 0xFF; quad->rgbGreen ^= 0xFF; quad->rgbRed ^= 0xFF; } quad++; } } free(line_and); return dib32; } return dib; } static FIBITMAP * DLL_CALLCONV Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { if (page == -1) { page = 0; } BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; if (handle != NULL) { FIBITMAP *dib = NULL; // get the icon header ICONHEADER *icon_header = (ICONHEADER*)data; if (icon_header) { // load the icon descriptions ICONDIRENTRY *icon_list = (ICONDIRENTRY*)malloc(icon_header->idCount * sizeof(ICONDIRENTRY)); if(icon_list == NULL) { return NULL; } io->seek_proc(handle, sizeof(ICONHEADER), SEEK_SET); io->read_proc(icon_list, icon_header->idCount * sizeof(ICONDIRENTRY), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapIconDirEntries(icon_list, icon_header->idCount); #endif // load the requested icon if (page < icon_header->idCount) { // seek to the start of the bitmap data for the icon io->seek_proc(handle, icon_list[page].dwImageOffset, SEEK_SET); if( IsPNG(io, handle) ) { // Vista icon support // see http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx dib = FreeImage_LoadFromHandle(FIF_PNG, io, handle, header_only ? FIF_LOAD_NOPIXELS : PNG_DEFAULT); } else { // standard icon support // see http://msdn.microsoft.com/en-us/library/ms997538.aspx // see http://blogs.msdn.com/b/oldnewthing/archive/2010/10/18/10077133.aspx dib = LoadStandardIcon(io, handle, flags, header_only); } free(icon_list); return dib; } else { free(icon_list); FreeImage_OutputMessageProc(s_format_id, "Page doesn't exist"); } } else { FreeImage_OutputMessageProc(s_format_id, "File is not an ICO file"); } } return NULL; } // ---------------------------------------------------------- static BOOL SaveStandardIcon(FreeImageIO *io, FIBITMAP *dib, fi_handle handle) { BITMAPINFOHEADER *bmih = NULL; // write the BITMAPINFOHEADER bmih = FreeImage_GetInfoHeader(dib); bmih->biHeight *= 2; // height == xor + and mask #ifdef FREEIMAGE_BIGENDIAN SwapInfoHeader(bmih); #endif io->write_proc(bmih, sizeof(BITMAPINFOHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapInfoHeader(bmih); #endif bmih->biHeight /= 2; // write the palette data if (FreeImage_GetPalette(dib) != NULL) { RGBQUAD *pal = FreeImage_GetPalette(dib); FILE_BGRA bgra; for(unsigned i = 0; i < FreeImage_GetColorsUsed(dib); i++) { bgra.b = pal[i].rgbBlue; bgra.g = pal[i].rgbGreen; bgra.r = pal[i].rgbRed; bgra.a = pal[i].rgbReserved; io->write_proc(&bgra, sizeof(FILE_BGRA), 1, handle); } } // write the bits int width = bmih->biWidth; int height = bmih->biHeight; unsigned bit_count = bmih->biBitCount; unsigned line = CalculateLine(width, bit_count); unsigned pitch = CalculatePitch(line); int size_xor = height * pitch; int size_and = height * WidthBytes(width); // XOR mask #ifdef FREEIMAGE_BIGENDIAN if (bit_count == 16) { WORD pixel; for(unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { BYTE *line = FreeImage_GetScanLine(dib, y); for(unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { pixel = ((WORD *)line)[x]; SwapShort(&pixel); if (io->write_proc(&pixel, sizeof(WORD), 1, handle) != 1) return FALSE; } } } else #endif #if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB if (bit_count == 24) { FILE_BGR bgr; for(unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { BYTE *line = FreeImage_GetScanLine(dib, y); for(unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { RGBTRIPLE *triple = ((RGBTRIPLE *)line)+x; bgr.b = triple->rgbtBlue; bgr.g = triple->rgbtGreen; bgr.r = triple->rgbtRed; if (io->write_proc(&bgr, sizeof(FILE_BGR), 1, handle) != 1) return FALSE; } } } else if (bit_count == 32) { FILE_BGRA bgra; for(unsigned y = 0; y < FreeImage_GetHeight(dib); y++) { BYTE *line = FreeImage_GetScanLine(dib, y); for(unsigned x = 0; x < FreeImage_GetWidth(dib); x++) { RGBQUAD *quad = ((RGBQUAD *)line)+x; bgra.b = quad->rgbBlue; bgra.g = quad->rgbGreen; bgra.r = quad->rgbRed; bgra.a = quad->rgbReserved; if (io->write_proc(&bgra, sizeof(FILE_BGRA), 1, handle) != 1) return FALSE; } } } else #endif #if defined(FREEIMAGE_BIGENDIAN) || FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB { #endif BYTE *xor_mask = FreeImage_GetBits(dib); io->write_proc(xor_mask, size_xor, 1, handle); #if defined(FREEIMAGE_BIGENDIAN) || FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB } #endif // AND mask BYTE *and_mask = (BYTE*)malloc(size_and); if(!and_mask) { return FALSE; } if(FreeImage_IsTransparent(dib)) { if(bit_count == 32) { // create the AND mask from the alpha channel int width_and = WidthBytes(width); BYTE *and_bits = and_mask; // clear the mask memset(and_mask, 0, size_and); for(int y = 0; y < height; y++) { RGBQUAD *bits = (RGBQUAD*)FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { if(bits[x].rgbReserved != 0xFF) { // set any transparent color to full transparency and_bits[x >> 3] |= (0x80 >> (x & 0x7)); } } and_bits += width_and; } } else if(bit_count <= 8) { // create the AND mask from the transparency table BYTE *trns = FreeImage_GetTransparencyTable(dib); int width_and = WidthBytes(width); BYTE *and_bits = and_mask; // clear the mask memset(and_mask, 0, size_and); switch(FreeImage_GetBPP(dib)) { case 1: { for(int y = 0; y < height; y++) { BYTE *bits = (BYTE*)FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { // get pixel at (x, y) BYTE index = (bits[x >> 3] & (0x80 >> (x & 0x07))) != 0; if(trns[index] != 0xFF) { // set any transparent color to full transparency and_bits[x >> 3] |= (0x80 >> (x & 0x7)); } } and_bits += width_and; } } break; case 4: { for(int y = 0; y < height; y++) { BYTE *bits = (BYTE*)FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { // get pixel at (x, y) BYTE shift = (BYTE)((1 - x % 2) << 2); BYTE index = (bits[x >> 1] & (0x0F << shift)) >> shift; if(trns[index] != 0xFF) { // set any transparent color to full transparency and_bits[x >> 3] |= (0x80 >> (x & 0x7)); } } and_bits += width_and; } } break; case 8: { for(int y = 0; y < height; y++) { BYTE *bits = (BYTE*)FreeImage_GetScanLine(dib, y); for(int x = 0; x < width; x++) { // get pixel at (x, y) BYTE index = bits[x]; if(trns[index] != 0xFF) { // set any transparent color to full transparency and_bits[x >> 3] |= (0x80 >> (x & 0x7)); } } and_bits += width_and; } } break; } } } else { // empty AND mask memset(and_mask, 0, size_and); } io->write_proc(and_mask, size_and, 1, handle); free(and_mask); return TRUE; } static BOOL DLL_CALLCONV Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { ICONHEADER *icon_header = NULL; std::vector vPages; int k; if(!dib || !handle || !data) { return FALSE; } // check format limits unsigned w = FreeImage_GetWidth(dib); unsigned h = FreeImage_GetHeight(dib); if((w < 16) || (w > 256) || (h < 16) || (h > 256) || (w != h)) { FreeImage_OutputMessageProc(s_format_id, "Unsupported icon size: width x height = %d x %d", w, h); return FALSE; } if (page == -1) { page = 0; } // get the icon header icon_header = (ICONHEADER*)data; try { FIBITMAP *icon_dib = NULL; // load all icons for(k = 0; k < icon_header->idCount; k++) { icon_dib = Load(io, handle, k, flags, data); if(!icon_dib) { throw FI_MSG_ERROR_DIB_MEMORY; } vPages.push_back(icon_dib); } // add the page icon_dib = FreeImage_Clone(dib); vPages.push_back(icon_dib); icon_header->idCount++; // write the header io->seek_proc(handle, 0, SEEK_SET); #ifdef FREEIMAGE_BIGENDIAN SwapIconHeader(icon_header); #endif io->write_proc(icon_header, sizeof(ICONHEADER), 1, handle); #ifdef FREEIMAGE_BIGENDIAN SwapIconHeader(icon_header); #endif // write all icons // ... // save the icon descriptions ICONDIRENTRY *icon_list = (ICONDIRENTRY *)malloc(icon_header->idCount * sizeof(ICONDIRENTRY)); if(!icon_list) { throw FI_MSG_ERROR_MEMORY; } memset(icon_list, 0, icon_header->idCount * sizeof(ICONDIRENTRY)); for(k = 0; k < icon_header->idCount; k++) { icon_dib = (FIBITMAP*)vPages[k]; // convert internal format to ICONDIRENTRY // take into account Vista icons whose size is 256x256 const BITMAPINFOHEADER *bmih = FreeImage_GetInfoHeader(icon_dib); icon_list[k].bWidth = (bmih->biWidth > 255) ? 0 : (BYTE)bmih->biWidth; icon_list[k].bHeight = (bmih->biHeight > 255) ? 0 : (BYTE)bmih->biHeight; icon_list[k].bReserved = 0; icon_list[k].wPlanes = bmih->biPlanes; icon_list[k].wBitCount = bmih->biBitCount; if( (icon_list[k].wPlanes * icon_list[k].wBitCount) >= 8 ) { icon_list[k].bColorCount = 0; } else { icon_list[k].bColorCount = (BYTE)(1 << (icon_list[k].wPlanes * icon_list[k].wBitCount)); } // initial guess (correct only for standard icons) icon_list[k].dwBytesInRes = CalculateImageSize(icon_dib); icon_list[k].dwImageOffset = CalculateImageOffset(vPages, k); } // make a room for icon dir entries, until later update const long directory_start = io->tell_proc(handle); io->write_proc(icon_list, sizeof(ICONDIRENTRY) * icon_header->idCount, 1, handle); // write the image bits for each image DWORD dwImageOffset = (DWORD)io->tell_proc(handle); for(k = 0; k < icon_header->idCount; k++) { icon_dib = (FIBITMAP*)vPages[k]; if((icon_list[k].bWidth == 0) && (icon_list[k].bHeight == 0)) { // Vista icon support FreeImage_SaveToHandle(FIF_PNG, icon_dib, io, handle, PNG_DEFAULT); } else { // standard icon support // see http://msdn.microsoft.com/en-us/library/ms997538.aspx SaveStandardIcon(io, icon_dib, handle); } // update ICONDIRENTRY members DWORD dwBytesInRes = (DWORD)io->tell_proc(handle) - dwImageOffset; icon_list[k].dwImageOffset = dwImageOffset; icon_list[k].dwBytesInRes = dwBytesInRes; dwImageOffset += dwBytesInRes; } // update the icon descriptions const long current_pos = io->tell_proc(handle); io->seek_proc(handle, directory_start, SEEK_SET); #ifdef FREEIMAGE_BIGENDIAN SwapIconDirEntries(icon_list, icon_header->idCount); #endif io->write_proc(icon_list, sizeof(ICONDIRENTRY) * icon_header->idCount, 1, handle); io->seek_proc(handle, current_pos, SEEK_SET); free(icon_list); // free the vector class for(k = 0; k < icon_header->idCount; k++) { icon_dib = (FIBITMAP*)vPages[k]; FreeImage_Unload(icon_dib); } return TRUE; } catch(const char *text) { // free the vector class for(size_t k = 0; k < vPages.size(); k++) { FIBITMAP *icon_dib = (FIBITMAP*)vPages[k]; FreeImage_Unload(icon_dib); } FreeImage_OutputMessageProc(s_format_id, text); return FALSE; } } // ========================================================== // Init // ========================================================== void DLL_CALLCONV InitICO(Plugin *plugin, int format_id) { s_format_id = format_id; plugin->format_proc = Format; plugin->description_proc = Description; plugin->extension_proc = Extension; plugin->regexpr_proc = RegExpr; plugin->open_proc = Open; plugin->close_proc = Close; plugin->pagecount_proc = PageCount; plugin->pagecapability_proc = NULL; plugin->load_proc = Load; plugin->save_proc = Save; plugin->validate_proc = Validate; plugin->mime_proc = MimeType; plugin->supports_export_bpp_proc = SupportsExportDepth; plugin->supports_export_type_proc = SupportsExportType; plugin->supports_icc_profiles_proc = NULL; plugin->supports_no_pixels_proc = SupportsNoPixels; }