convertImage/main.c
/* |
File: main.c |
Abstract: |
This sample shows how to convert any image to sRGB applying Black Point |
Compensation (BPC) using ImageIO, ColorSync and vImage. |
To build from the command line: |
cc -g -O0 -o convertimage convertimage.c -Wall -framework ApplicationServices -framework Accelerate |
To run from the command line: |
./convertimage "path to source image" "path to destination image" |
e.g. ./convertimage myCMYKImage.jpg mySRGBImage.jpg |
Version: 1.0 |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Inc. ("Apple") in consideration of your agreement to the following |
terms, and your use, installation, modification or redistribution of |
this Apple software constitutes acceptance of these terms. If you do |
not agree with these terms, please do not use, install, modify or |
redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Inc. may |
be used to endorse or promote products derived from the Apple Software |
without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or |
implied, are granted by Apple herein, including but not limited to any |
patent rights that may be infringed by your derivative works or by other |
works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright (C) 2013 Apple Inc. All Rights Reserved. |
*/ |
#include <stdlib.h> |
#include <Accelerate/Accelerate.h> |
#include <ApplicationServices/ApplicationServices.h> |
/* Enable (1) or Disable (0) blackpoint compensation for output */ |
#define BPC_ON (1) |
/* --------------------------------------------------------- |
** readImage |
** |
** Creates and returns a CGImageRef and type identifier for the |
** image specified by the provided image path. |
** ------------------------------------------------------- */ |
static CGImageRef readImage(const char* imagePath, CFStringRef* imageType) |
{ |
CGImageRef image = NULL; |
CFStringRef cfpath = CFStringCreateWithCString(NULL, imagePath, kCFStringEncodingUTF8); |
if (cfpath) |
{ |
CFURLRef url; |
CGImageSourceRef source; |
url = CFURLCreateWithFileSystemPath(NULL, cfpath, kCFURLPOSIXPathStyle, 0); |
CFRelease(cfpath); |
/* Create an image source reading from `url' */ |
source = CGImageSourceCreateWithURL (url, NULL); |
/* create CGImageRef for the image source */ |
image = CGImageSourceCreateImageAtIndex(source, 0, NULL); |
/* get and return type identitier of image source */ |
if (imageType) *imageType = CGImageSourceGetType(source); |
/* cleanup */ |
if (url) |
CFRelease(url); |
CFRelease (source); |
} |
return image; |
} |
/* --------------------------------------------------------- |
** createColorSyncCodeFragment |
** |
** Creates a ColorSync transform with optional black point |
** compensation for a given source and destination profile. |
** Returns a code fragment from this transform for the full |
** color conversion. |
** The BPC parameter passed to this function determines |
** whether to enable or disable black point compensation |
** during the conversion. |
** ------------------------------------------------------- */ |
static CFTypeRef createColorSyncCodeFragment (ColorSyncProfileRef srcProfile, ColorSyncProfileRef dstProfile, bool BPC) |
{ |
CFTypeRef codeFragment = NULL; |
if (srcProfile && dstProfile) |
{ |
/* specify keys that define the profile object and the information on the usage of the profile in the transform. These are needed when creating the actual transform below. */ |
const void *keys[] = { |
kColorSyncProfile, |
kColorSyncRenderingIntent, |
kColorSyncTransformTag, |
kColorSyncBlackPointCompensation |
}; |
const void *srcVals[] = { |
srcProfile, |
/* BPC uses relative colorimetric intent for conversions */ |
BPC ? kColorSyncRenderingIntentRelative: kColorSyncRenderingIntentPerceptual, |
kColorSyncTransformDeviceToPCS, |
BPC ? kCFBooleanTrue : kCFBooleanFalse |
}; |
const void *dstVals[] = { |
dstProfile, |
/* BPC uses relative colorimetric intent for conversions */ |
BPC ? kColorSyncRenderingIntentRelative: kColorSyncRenderingIntentPerceptual, |
kColorSyncTransformPCSToDevice, |
BPC ? kCFBooleanTrue : kCFBooleanFalse |
}; |
/* create a dictionary of the transform keys for the source and destination |
profiles. */ |
CFDictionaryRef srcDict = CFDictionaryCreate ( |
NULL, |
(const void **)keys, |
(const void **)srcVals, |
4, |
&kCFTypeDictionaryKeyCallBacks, |
&kCFTypeDictionaryValueCallBacks); |
CFDictionaryRef dstDict = CFDictionaryCreate ( |
NULL, |
(const void **)keys, |
(const void **)dstVals, |
4, |
&kCFTypeDictionaryKeyCallBacks, |
&kCFTypeDictionaryValueCallBacks); |
const void* arrayVals[] = {srcDict, dstDict, NULL}; |
CFArrayRef profileSequence = CFArrayCreate(NULL, (const void **)arrayVals, 2, &kCFTypeArrayCallBacks); |
if (srcDict) CFRelease (srcDict); |
if (dstDict) CFRelease (dstDict); |
/* create the transform to be used for converting color */ |
ColorSyncTransformRef transform = ColorSyncTransformCreate (profileSequence, NULL); |
if (profileSequence) CFRelease (profileSequence); |
/* get the code fragment specifying the full conversion */ |
codeFragment = ColorSyncTransformCopyProperty(transform, kColorSyncTransformFullConversionData, NULL); |
if (transform) CFRelease (transform); |
#if DEBUG |
if (codeFragment) CFShow(codeFragment); |
#endif |
} |
return codeFragment; |
} |
/* --------------------------------------------------------- |
** initvImageCGImageFormat |
** |
** Populates a vImage_CGImageFormat with parameters from a CGImageRef. |
** Note: the colorspace and decode array are owned by the CGImage. |
** If you need the vImage_CGImageFormat to outlast the CGImage, retain |
** the colorspace and copy the decode array. |
** ------------------------------------------------------- */ |
static void initvImageCGImageFormat(CGImageRef image, vImage_CGImageFormat* format) { |
format->bitsPerComponent = (unsigned int)CGImageGetBitsPerComponent(image); |
format->bitsPerPixel = (unsigned int)CGImageGetBitsPerPixel(image); |
format->colorSpace = CGImageGetColorSpace(image); |
format->bitmapInfo = CGImageGetBitmapInfo(image); |
format->renderingIntent = CGImageGetRenderingIntent(image); |
format->version = 0; |
format->decode = CGImageGetDecode(image); |
} |
/* --------------------------------------------------------- |
** convertImage |
** |
** Use vImage and the specified code fragment transform to |
** convert the input image to the destination color space. |
** |
** Note that this method will have some other side effects. |
** It will flatten any image masks into the image. If output |
** format has no alpha and an image mask is present then a |
** flattening operation will be performed against the background |
** color and the alpha information will be removed entirely |
** from the image. This is easily corrected by adding an alpha |
** channel if there isn’t one to the output format. It will also |
** lose track of any other metadata attached to the original |
** image because you are creating a new CGImageRef from what is |
** just a pile of pixels. Use ImageIO to access original image |
** metadata if needed, and possibly add relevant parts to the |
** output image. |
** ------------------------------------------------------- */ |
static CGImageRef convertImage (CGImageRef inpImage, ColorSyncProfileRef dstProfile, CFTypeRef codeFragment) |
{ |
CGImageRef outImage = NULL; |
/* Initialize source buffer */ |
vImage_Buffer buf; /* a temporary buffer to hold the post conversion results */ |
/* Input format of image. If it doesn't match the native format of the image, vImageBuffer_InitWithCGImage |
will convert to the format specified. */ |
vImage_CGImageFormat inpFormat; |
memset(&inpFormat, 0, sizeof(vImage_CGImageFormat)); |
/* Which format is set here is unimportant. If your image processing pipeline demands a specific format, use that. */ |
initvImageCGImageFormat(inpImage, &inpFormat); |
/* decode input image and convert to vImage_Buffer of the requested format. buf.data will be overwritten with a malloc allocated pointer to the image data. The height, width and rowBytes of the image are also updated. */ |
vImage_Error err = vImageBuffer_InitWithCGImage( &buf, &inpFormat, NULL, inpImage, kvImageNoFlags ); |
/* If that worked, convert to the new format using the codeFragment transform provided. */ |
if (err == kvImageNoError) |
{ |
/* Initialize destination buffer */ |
CGColorSpaceRef dstColorSpace = CGColorSpaceCreateWithPlatformColorSpace (dstProfile); |
CGFloat* backgroundColor = NULL; /* provide background color if needed */ |
vImage_CGImageFormat outFormat = |
{ |
.bitsPerComponent = (unsigned int)CGImageGetBitsPerComponent(inpImage), /* preserve original */ |
.bitsPerPixel = (unsigned int)CGImageGetBitsPerComponent(inpImage) * (unsigned int)(CGColorSpaceGetNumberOfComponents( dstColorSpace ) + (kCGImageAlphaNone != CGImageGetAlphaInfo(inpImage))), |
.colorSpace = dstColorSpace, /* colorspace here should have same number of channels as destination image in code fragment */ |
.bitmapInfo = CGImageGetBitmapInfo(inpImage),/* copy from the original */ |
.version = 0, |
.decode = NULL, /* non-NULL if you want a decode array, or for float L*a*b* which needs one */ |
.renderingIntent = kCGRenderingIntentDefault /* irrelevant here ; it's applied by codeFragment */ |
}; |
/* Attempt to run faster by using the conversion in place. This touches less memory and can run quicker. */ |
vImage_Buffer buf2 = buf; /* temporarily set buf2 to be an alias to buf */ |
/* create a converter to do the (BPC) conversion. */ |
vImageConverterRef converter = vImageConverter_CreateWithColorSyncCodeFragment( codeFragment, &inpFormat, &outFormat, backgroundColor, kvImageNoFlags, &err ); |
/* Check to see if the converter will work in place. */ |
vImage_Error outOfPlace = vImageConverter_MustOperateOutOfPlace(converter, NULL, NULL, kvImageNoFlags); |
/* if it doesn't work in place, we change buf2 from an alias to buf to use its own independent buffer */ |
if (outOfPlace) |
err = vImageBuffer_Init( &buf2, buf.height, buf.width, outFormat.bitsPerPixel, kvImageNoFlags ); /* allocate a second buffer */ |
/* Do the conversion */ |
if (err == kvImageNoError) |
/* you will get kvImageInvalidParameter if backgroundColor is NULL |
and the conversion needed a backgroundColor */ |
err = vImageConvert_AnyToAny( converter, &buf, &buf2, NULL, kvImageNoFlags ); |
/* Make a new image */ |
/* Wrap the image created in buf2 with a CGImageRef. Here we pass kvImageNoAllocate to cause the CGImageRef to use the buf2.data directly as its underlying pixel buffer. This avoids a data copy. Instead the ownership of that piece of memory is transferred to the CGImageRef. If the memory needed a custom deallocator (that is, not free()) then you can pass a non-NULL deallocator callback here. In this example, we pass NULL, because the buffer was initialized by either vImageBuffer_Init or vImageBuffer_InitWithCGImage */ |
if (err == kvImageNoError) |
outImage = vImageCreateCGImageFromBuffer( &buf2, &outFormat, NULL, NULL, kvImageNoAllocate, &err ); /* assumes ownership of buf2.data */ |
/* in the case that we couldn't run in place, buf2 didn't alias buf, so we need to free buf.data */ |
if (outOfPlace) |
free(buf.data); |
/* cleanup the colorspace we created above */ |
if (dstColorSpace) |
CFRelease(dstColorSpace); |
/* cleanup the converter. Note: that if you plan to process a bunch of images in this way, it would be more efficient to reuse the converter rather than creating a new one each time. vImageConverterRefs that do colorspace conversion can be expensive to create, particularly those that involve multidimensional lookup tables, typically those that convert to/from CMYK. */ |
vImageConverter_Release(converter); |
} |
return outImage; |
} |
/* --------------------------------------------------------- |
** saveImage |
** |
** Saves a CGImage of the designated type in the location |
** specified by the image path. |
** ------------------------------------------------------- */ |
static void saveImage(CGImageRef image, const char* imagePath, CFStringRef imageType) |
{ |
CFStringRef cfpath; |
cfpath = CFStringCreateWithCString(NULL, imagePath, kCFStringEncodingUTF8); |
if (cfpath && image) |
{ |
CGImageDestinationRef dest; |
/* create URL for the file system path */ |
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, |
cfpath, |
kCFURLPOSIXPathStyle, |
0); |
/* Create an image destination writing to `url' */ |
dest = CGImageDestinationCreateWithURL(url, imageType, 1, NULL); |
/* Set the next image in the image destination */ |
CGImageDestinationAddImage(dest, image, NULL); |
/* Write everything to the destination url */ |
CGImageDestinationFinalize(dest); |
CFRelease(url); |
CFRelease(dest); |
} |
if (cfpath != NULL) |
CFRelease(cfpath); |
} |
/* --------------------------------------------------------- |
** main |
** |
** Convert input image to the sRGB color space and apply |
** black point compensation. |
** ------------------------------------------------------- */ |
int main (int argc, char **argv) |
{ |
if (argc != 3) |
{ |
fprintf (stderr, "Usage: convertimage INPUT_IMAGE OUTPUT_IMAGE\n"); |
return (-1); |
} |
const char* srcImagePath = argv[1]; |
const char* dstImagePath = argv[2]; |
CFStringRef imageType = NULL; |
CGImageRef srcImage = readImage (srcImagePath, &imageType); |
CFDataRef srcProfileData = CGColorSpaceCopyICCProfile (CGImageGetColorSpace(srcImage)); |
/* create profile based on the source image color space */ |
ColorSyncProfileRef srcProfile = ColorSyncProfileCreate (srcProfileData, NULL); |
if (srcProfileData) |
CFRelease (srcProfileData); |
/* create sRGB destination profile */ |
ColorSyncProfileRef dstProfile = ColorSyncProfileCreateWithName(kColorSyncSRGBProfile); |
/* create a ColorSync transform with optional black point compensation (BPC) for the given |
source and destination profiles */ |
CFTypeRef codeFragment = createColorSyncCodeFragment (srcProfile, dstProfile, BPC_ON /* BPC */); |
if (srcProfile) |
CFRelease(srcProfile); |
/* convert source image to the destination color space using black point compensation */ |
CGImageRef dstImage = convertImage (srcImage, dstProfile, codeFragment); |
/* cleanup */ |
if (srcImage) |
CFRelease(srcImage); |
if (dstProfile) |
CFRelease(dstProfile); |
if (codeFragment) |
CFRelease (codeFragment); |
/* save the converted image */ |
saveImage (dstImage, dstImagePath, imageType); |
if (dstImage) |
CFRelease (dstImage); |
return 0; |
} |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-07-30