Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2006-201
$eventId
ID of event: wwdc2006
$eventContentId
ID of session without event part: 201
$eventShortId
Shortened ID of event: wwdc06
$year
Year of session: 2006
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC06 • Session 201

Modern Image Handling with the Image I/O Framework

Graphics and Media • 45:36

The Image I/O framework is the definitive technology for accessing pixels and metadata from image files. Its high-performance architecture supports reading and writing standard image file formats, digital camera RAW files, and specialized formats such as OpenEXR. Learn how to use Image I/O to modernize image handling, increase loading performance, and support popular image metadata tags. If you work directly with image data, this is one session that you won't want to miss.

Speakers: David Hayward, Stan Jirman

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hello and welcome to the last session of the day where we'll be talking about advanced image handling with Image I/O Framework. And just a reminder, just because the slide up here says to refrain from taking pictures, that just refers to the presentation. Other than that, we really want you to take pictures because we have some great imaging in our system and we want to make sure you take advantage of it.

So, first of all, my name is David Hayward. I'm an engineer and manager in the graphics and imaging group at Apple and I work on Image I/O and RAW imaging as well. And we have a lot to talk about today. We'll be giving an architectural overview of image handling in the system, talking about opening images, displaying images, adjusting RAW images in particular. There's lots of exciting new features that we have there. And last but not least, you've got to save your work, so saving images.

So first off, the architectural overview. When we first added Image I/O Framework in Tiger in the last release, we had several key features we wanted to achieve, and these are still true today. We wanted to be able to write many file formats, read many file formats, support reading and writing of key metadata, support automatic color management through ICC profiles and the like.

We also want to support processing RAW images, which is an emerging field that has all sorts of interesting ramifications on image processing. Also, we wanted to be able to support other features such as incremental loading for web browsers and advanced image formats such as floating point pixel file formats.

One of the first questions people ask me about Image I/O is often, "Well, what file formats do you support?" Well, we support all the common web standard file formats such as TIFF, JPEG, PING, GIF, JPEG 2000. We actually have a new version of JPEG 2000 in the seed that you have.

Also, we support several advanced floating-point, high-dynamic range file formats such as OpenEXR, Radiance, and varieties of TIFFs such as FloatTIFF, LogLuvTIFF, and PixartTIFF encodings. And of course, we support a whole bunch of RAW file formats, and this is something we've been spending a lot of time on in the last year or so, from a wide variety of camera vendors.

There's also a wide variety of legacy file formats that we all use frequently, which is BMP formats, Photoshop documents, QTIF, you name it. And in the subject of metadata, there's a wide variety of metadata standards that are in wide use. And we support many of those, such as EXIF, IPTC, GPS metadata, and some vendor-specific maker notes. And of course, this is something that's going to be continuing to grow in the future, especially in the area of RAW file formats. We've been adding support in every major release and also a lot of the software updates as well.

So how does all this fit into the overall system? Well, first we start with wherever possible open source implementations, such as libTIFF and libJPEG and libPNG. Other file formats we have our own code for. And all of these file formats we wrap in an API we refer to as the Image I/O Framework.

And this allows your application to have a single common interface for reading and writing file formats across all these varieties. These are built on the foundation of Darwin, and on top of the Image I/O layer, we have higher-level graphics APIs, such as Core Image and Quartz, which make use of Image I/O. Above that in the stack, we have Carbon and Cocoa frameworks, which also make use of Image I/O indirectly.

And last but not least, we have applications, many of which in the system are using Image I/O either directly or implicitly through the higher-level frameworks. So as you might guess by this stack, that there is a variety of APIs that we have on our system for accessing Image I/O.

And some of the key data types that keep in mind are that a modern graphic system needs to have data types for color space, a way of representing bitmap images, a way of representing individual colors for vector artwork, and also a context of some sort. And of course, we have a whole stack of APIs, and your choice as to which one you want to use, all of these have these basic constructs. What we'll be talking about today are the core image and quartz data types in particular. I should mention that in many cases, there are very easy ways of converting between these data types. We'll talk more about that later.

So before I go into more detail about Image I/O and how it's implemented, let's just briefly get a discussion of what's actually in a typical image file. Well, the key requirements for an image are its geometry, i.e. its height and its width and its pixel depth and other information like that, its color space, which describes the color characteristics of the pixel values, and the actual pixel data. Then there's some optional information, which is nice to have where possible, which is embedded thumbnails and metadata.

Some file formats also support multiple images per file, and that's important to keep in mind. Formats such as GIF and TIFF come to mind. Because there can be multiple images in a file, there are some properties that actually transcend any individual image. These are properties such as the actual file format itself and other file properties. For example, GIF has some properties that apply to the entire animation rather than to any one single frame.

So how do we expose this in the Image I/O API? Well, the file type of the file is represented as a CFStringRef, which is a UTI. The file properties is a dictionary, CFDictionary. The image attributes, such as the geometry, color space, and pixel data, are well encapsulated by the CGImageRef description. It can describe a wide variety of pixel formats. Thumbnails and others, optional CGImageRef. And the metadata is, again, a dictionary.

So those are the basics of what makes up an image and the basic ideas of Image I/O. Let's talk first about how to open images with Image I/O. So the basic steps involved with reading an image involve parsing the file and getting key information from that file, such as decompressing the pixel data, determining what the color space of that file is, and extracting the metadata.

So this can be provided via a small set of APIs in Image I/O. It's very simple. First off is you should get a list of what file formats Image I/O can read. This is provided by returning a UTI array using the API CG Image Source Copy Type Identifiers . And this is a good thing for your application to take advantage of because we are, as I mentioned earlier, constantly adding new file formats. And if you make use of this API, your application can grow to use those formats without having to rev.

I should also mention at this point that in the seed that you have, some of the file formats are not supported, especially the legacy formats, are not supported on 64, just a few of them, 64-bit. So again, it's a good idea to call this API because it will tell you what formats are supported under your current instruction set.

Once you know what kind of files you can open, you can open a file using CG Image Source, Create with URL, or Create with Data, or Create with Data Provider. This returns an opaque object from which you can get critical information such as the type of the file that the image actually is and the number of images that are contained within it. Then for each image within that image source, you can get the actual image by calling CGImageSource, CreateImageAtIndex, get the metadata by calling CGImageSource, CopyPropertiesAtIndex, and you guessed it, you can get the thumbnail by calling CGImageSource, CreateThumbnailAtIndex.

So this is a minimal code snippet that just shows how easy it is to use Image I/O. This function, the idea behind it is to, given a URL, return a CG image ref. And all we do is, given that URL, we call CG image source, create with URL. Then we call CG image source, create image and index, passing in the index 0. That's it. It's very simple.

One thing to be aware of, however, is that file formats come in a variety of flavors, and some file formats support only one pixel depth. For example, JPEG is always eight bits per sample. Other file formats support a variety of pixel depths, such as TIFF, which can be arbitrarily depth. One thing to keep in mind is that as a rule, Image I/O will return a CG image ref with the same depth as the original file.

However, in the case of high dynamic range or floating point formats, things are slightly more complicated. In this case, the data that's in the file is often specially encoded or packed. And the open source code that's available to decompress that data often has several options for how to interpret that data. Often the data can either be unpacked as floats or integers, and also with different depths, either half floats or full floats, or 8-bit integers or 16-bit integers.

Also, the data can be either returned as extended range or tone compressed into the logical range of 0 to 1. So there's already a lot of options for floating point images that we didn't have to deal with for some of the more traditional file formats. And our choice with Image I/O was to always return a CG image ref that's tone compressed into 16-bit images.

This produces good results for a wide variety of applications. However, if your application wants to get direct access to the floating point data, you can opt into this by request, and you can get extended range floats. This is a simple code snippet that shows how easy it is to do this. Again, it's very similar to the previous code snippet. The only difference is that we're passing in an options dictionary to Image I/O to change the behavior.

In this case, we're passing an option dictionary that contains one key value. The CG image source should allow float, and the Boolean true is the value for that. So if we want to get the image back, we can test to see if it actually is float by calling cgimage, get bitmap info, and seeing if the float attribute is set on it.

So next I'd like to talk a little bit more about metadata. When Image I/O returns metadata, it returns it as a dictionary. And this dictionary contains, at the root level of the dictionary, general properties, which are common to a wide variety of file formats. These are properties such as height, width, orientation, and color space. This provides easy access for applications to get this general information. The metadata dictionary also contains sub-dictionaries, however, and these are sub-dictionaries for metadata file formats such as TIFF, EXIF, IPTC, and GPS. And these are grouped into sub-dictionaries.

One thing we did when we designed these dictionaries is we tried wherever possible to keep the keys and values in these dictionaries as close as possible to what's actually in the specification. This means, for example, when you get the TIFF properties, there'll be a property called Resolution Unit, and the values for that will be a number such as 1, 2, or 3, which is consistent with the TIFF spec. Or, in another little crazier example, the compression value can be arbitrary values such as 1, 5, or even seemingly random numbers like 3, 2, 7, 7, 3.

It is up to your application or the client to localize the keys and the values as you see fit in your application. For example, your application might want to display the resolution unit in the more human understandable values such as none, inch, or cm. Or in the case of compression, in this example, no compression, LCW compression, or packed bits.

So again, here's a very simple code snippet for how to get metadata out of an image using Image I/O. We can call CGImageSource create with URL, as we did before. And now we call CGImageSource copy properties at index. And this returns a dictionary. In this sample, all we're doing is getting some of the general properties out of the file, such as the property DPI width, DPI height, and orientation. These three properties are actually critical for the proper display of your image. I'll talk more about that in a bit.

Back to the subject of thumbnails for a bit. Image I/O has some very flexible support for providing thumbnails for images. And the reason we provided this is that there's a wide variety of needs that applications have for thumbnails. Some applications want something that's very quick. Other applications might want something that's better quality for thumbnails.

And there's no one right answer for all applications. There's sort of a hierarchy of needs for thumbnails. Sometimes all an application wants is just a generic icon for the file. That's very fast, it's small size, but it's not at all representative of the actual content in the file.

In between, there is some image file formats support embedded thumbnails. These are fast, and typically it only requires reading the first fraction of the file to find the embedded thumbnail. They're small size, which can be good, typically 160 by 120 pixels. But they may look different than the actual image content, so that's something to be aware of. Lastly, the best level of thumbnail is to actually ask for a reduced representation of the full image.

This may be slower than in any of the previous methods, but they can be arbitrary size, and they will look very close to the actual image. And when thumbnail-ing in this mode, Image I/O takes whatever tricks are possible to try to return an image that's sufficiently good quality for the size it's requested.

So here's a short example for how your application can get thumbnails. And in one call, you can kind of get some of the best of both worlds in this example. We again create an image source with URL, and we're passing in two key values. The first is the property CGImageSource createThumbnailFromImageIfAbsent. What this means is that an image will be returned quickly from the embedded thumbnail if it's present, but if not, it will return it from the full-size image. So this is useful for a lot of quick cases.

Second, we specify that we want the image to be no bigger than 160 pixels. Again, this is useful because a lot of applications want to limit the size of their thumbnails to something reasonable. Once we create the option dictionary with these two keys, we call CG Image Source Create Thumbnail at index.

So I'm going to give a quick demo of this in practice. So if you've been to any other graphic sessions here, there's been a lot of examples showing arrays of images in thumbnail browsers. And one of the great features in Leopard is ImageKit's new icon browser, which does such a great job of doing thumbnails. It actually uses Image I/O to look for the embedded thumbnails first, and then on a separate thread, it brings in the higher quality images. It does a great job.

But for purposes of demonstration of how things work internally, it actually does too good a job, because it all does it transparently. So what I'd like to have here is a little sample application that can show the different levels of quality of thumbnails with some control that's given to me via sliders. What I have here is a folder of a whole bunch of images.

And this application is doing a lightweight, lazy loading of icons and showing some basic metadata information as well. One thing we can do is we can adjust the size, obviously. But this is just a very crude representation of the file. This is just the generic icon, which is very fast, but not very indicative of the content. So what we can do here is adjust the quality of the thumbnail.

And what you'll see here is I said that I would like to use the embedded thumbnail if present. And what you'll see here is now things look a little better. It's not the best resolution. You can see some pixelization on this thumbnail. And you'll also see that oftentimes with thumbnails, there's black bars at the top and bottom. So this is pretty good, and it's fast. You can see as we scroll through the image, they peel in very quickly.

It's not ideal. And so what this application also does is allows you to turn it up to render a reduced representation of the full-size image. And now you can see what's happened here is as we go to larger sizes, we see the full quality of the image. And again, things are pretty quick because we can take tricks wherever possible to produce images. You can see here it's slightly blurry, and when I let go, it'll come in a little sharper.

So we used whatever tricks possible. In the case of JPEGs, we can ask for reduced size JPEGs and reduce the computation considerably. Again, on the subject of metadata, if I select one of these images, this view over here shows all the metadata that's in that file. And this is a good illustration of the hierarchical dictionaries that we return. These are all the general properties such as height, width, orientation-- this one's rotated-- and the DPI. And then we have sub-dictionaries for all the separate subgroups of metadata such as EXIF, TIFF properties, and some Canon Maker notes.

In the code, this is very simple. This code is not yet on the CD, but we'll make it available. In this function here, we have a very simple function to get the embedded thumbnail of a given size. Again, all we're doing is calling CG Image Source Create Thumbnail at index, and we're passing in a couple properties.

One is we're specifying the size that we want it returned at. And also, we're saying we want the image auto-rotated if possible. This is another nice feature of Thumbnailing, which is if you pass in the CG Image Source Create Thumbnail with Transform, it'll automatically rotate the image so it looks the right orientation.

The higher fidelity image that we return, I refer to it as main image with size. And again, it's very similar. Again, we pass in an options dictionary. In this case, we say that we want to pass in the property CG image source, create thumbnail from image always. That's about it. So back to the slides.

So now that we know how to open images and get metadata from them, the next thing I'd like to talk about is displaying images. So these are the general steps that are involved with displaying an image. It is actually quite complex. It involves actions such as color conversion, bit depth conversion, geometry mapping, and then compositing on the existing background if transparency is available. The good thing is all of this complex functionality is provided via a single API in Core Graphics, which is CG Context Draw Image.

Now, one of the things about CG Context Draw Image is that it takes a CG image, but that doesn't mean Cocoa Apps can't use this API or use CG images directly. It's possible from an NSViewDrawRack method to get the CG context from the NSGraphics context. So this is a great thing because it allows Cocoa applications like the one I just showed to directly call CG image sources and get all the flexibility that's available to those.

So here's a brief code snippet that shows how we do this. We have a draw rec call. We get the current CG context by calling NSGraphicsContextCurrentContextGraphicsP ort. The next thing we're doing is we're getting the height and width of the image, and then we're going to get an image transform for the current view. I'll talk a little bit more about that later. We then concatenate that image transform, which is a CG affine transform, to the current context, and then we draw the image in the view.

One of the things we really don't want our users to see, however, is what we see here, which is-- and we've all seen this before-- where the images are not rotated correctly. And often this is the case because the image has metadata in it that says it needs to be rotated, but the application is not respecting that metadata.

Similarly, this is a slightly less common case. There are image formats that have non-square pixels. Again, these formats have metadata that say what the DPI and the vertical and horizontal direction are. But if your application doesn't respect that metadata, then you won't get the image you expect. And that's bad for our users.

So there's a very small amount of code that you can write that will correctly create a CGA-fine transform from the metadata. The three pieces of metadata that are critical are the DPI and the horizontal and vertical direction, and the orientation value. These orientation values are values between 1 and 8, and they're defined in the TIFF spec. They're very hard to predict what they actually mean.

There's no rhyme or reason to it as far as I can tell. But the key is that 1 means normal, and 2 means flip horizontal, and so forth. And the idea is to convert these values along with the DPI into a CTM. So this is a very handy function.

There's a couple other advanced techniques that I'd like to talk about as far as drawing images are concerned. One of the common questions I have is, well, how do I just get the RGB data out of an image? And that's a little bit tricky because in CG, CG image refs are opaque, immutable types. So the way to get data out of them is to actually render them into a bitmap context. This is actually a good thing because it forces the application to decide, well, I don't just want RGB data, but I want RGB data in a specific color space.

So in this case, we create a color space. In this case, we are asking for a generic RGB coordinates. We create a context with the appropriate size and a buffer of data and that color space. And we call the CG context Draw Image to draw the image into that context. So now once that draw is complete, we can release some stuff and the data is now available for the application to get the pixel data out of that image.

Another common subject for a more advanced rendering of images is what to deal with images that don't have embedded profiles. Often times there are legacy files that are still around that don't have embedded profiles. It's becoming increasingly rare. But there's no one right answer as far as how to render images without embedded profiles. And usually the best solution is to give your user the choice of how to handle this situation.

So this shows you how to write the code to assign default profiles. First of all, we have an image, and we need to determine whether it doesn't have a profile in it. And the way this code does this is to get the current color space from the image and see if the color space is one of the following: if it's either device gray, device RGB, or device CMYK.

If it's equal to any of these, then we're going to use a default color space instead. This code doesn't show how to ask the user and provide user interface, but we can leave that as an exercise. Once we have the default color space that we want to replace it with, we call CGImageCreateCopy with color space. And that's all there is to it.

Another important thing to keep in mind in terms of color management of images is to make sure that your images are matched correctly across displays. There's one line you can add to your NSView that will make this happen automatically, and that is tell the window that it wants to receive redraw events when the window's profile has changed or when the window moves to a different display. This is done with set displays when screen profile changes. This is all you need to do. What happens then is whenever the profile changes or the window moves, your application will automatically receive an event that needs to redraw that content.

The next thing I'd like to talk about today is a subject that's now near and dear to my heart, which is adjusting RAW images. RAW images are one of the interesting areas that have been growing up in the last couple years, and I'd like to talk about the fundamentals of RAW images, the architectural overview, and how you can use a new filter that we've provided in Leopard.

So here's a crash course on RAW images, some of the fundamentals. So a RAW image, a camera RAW file, contains minimally processed data from the actual sensor of a camera. What this means is that it's required to have special image processing in order to produce an image that's suitable for display or print.

There are several things involved in this process. First of all, the file needs to be decoded in order to get the actual bare sensor data out of the file. We need to extract critical metadata from the file in order to know how to process it correctly. And then there's a series of spatial reconstruction methods, such as debaring and compensating for noise and chroma blur and luma sharpening, and color processing, such as highlight recovery, adjusting exposure and temperature tint, converting from the scene-referred data to an output-referred color space. And as always, there's subjective aspects of this, and so there's a certain amount of subjective seasoning that can be added to this recipe as well to produce an image that's pleasing.

So all of this requires dozens of parameters. And in order to produce these images, Image I/O will maintain these dozens of parameters for all the camera models that have been qualified. We're also continuously improving our RAW processing methods, and what this means is that Image I/O maintains multiple method versions as well.

One thing to keep in mind is by default, Image I/O will return a CG image ref that's rendered according to the default parameters and according to the latest version. So this is great for most typical applications. However, the default parameters-- Cannot be perfect for all photographs or all photographers. Everyone's got different opinions and different images require different processing.

And this is one of the great things about RAW file formats, is that the user can have high-fidelity control over how the image is developed. This is the whole benefit. So to provide this benefit, we provided a new filter in Core Image called the CIRAW filter, which is designed to give your application and your users easy control over the RAW processing parameters. And also, we wanted to make sure it was fast and interactive and had high performance.

So here's roughly how this works. We start with a RAW image, which can be provided either as a URL or data. And we provide this as an input parameter into the CI RAW filter. Also into the CI RAW filter, we provide user adjustments, such as exposure and temperature tint. If these aren't provided, then the default values are used. Once these are provided, from the CI RAW filter, you can extract a CI image.

This CI image can be rendered to the display, and then based on the user feedback, the user can provide new slider positions for adjustments such as exposure and tint, and the process can repeat itself. There's another output, however, which is from a CI image, you can also create a CG image. And from that CG image, we can then produce a new file on disk. So both for interactive use on the display and also for final rendering to files, you can use this new CI RAW filter to provide user control over adjustments to the RAW processing.

This is a very, very brief code snippet that shows how this works. What we have as a function here, that instead of just returning the default RAW image, we'll provide an adjusted RAW image instead. We start with calling a CI filter, saying we want the filter with a URL and no options by default. Then we want to provide a default exposure value to override the normal exposure. In this case, we're specifying a value of -1 to make the image a little darker.

Once we've provided the inputs to the filter, we can now ask for a CI image to get for the output. And that we call just calling value for key and saying CI output image key. That's it. However, it's much better to show this in person because there's a lot more you can do with this. And I'd like to bring up Stan Jirman who will show it in my action.

Okay. Can you hear me now? I guess I can hear myself. So this is a demo app that you probably have seen in some other Core Image and other demos. I'll go into a little bit more detail here. It shows the capabilities of the CIRROW filter as it ships in your current developer preview.

Here we have an image of astronaut Alan Poindexter, who's showing me the inside of one of his two engines on his training jet. It's overexposed, which is why it ended up in the trash can, but it's really great for the demo because with a RAW filter, you can adjust the highlights in such a way that the sky retains the details in the clouds.

At the same time, you could, for instance, append a gamma filter that would allow you to reclaim some of the detail in the jet engine. Now, I don't know how much you can see on the projector, but if I overdo it a little bit, you see that you get the detail in the clouds at the same time as the detail in the engine.

You cannot do that with a non-RAW image, which is when you take the same image, convert it into TIFF in the default configuration, you can decrease the brightness all you want, or the exposure, and the clouds just will never come back. This also shows that you can use the CI RAW filter not just for RAW files.

You can use the CI RAW filter as the first pass for input for any images coming into your application. Just in the case of a non-RAW file, we will do the best we can with retaining or making the image behave as close as possible as what the equivalent RAW file would.

To go back, to a better example about white balance, everybody meet Rachel, you can click on any point in the image, for instance, and like her white shirt, and then the image will be adjusted to the appropriate white balance. You could not do that quite the same way if this image was a JPEG as an input.

You can also use Temptant sliders, which are always a little bit more nervous on the bottom end of the scale than on the high end of the scale, for temperature and tint. Now how does this work inside the application? Is this more or less legible to change the resolution at the last moment? Let me make it a little bit bigger.

Yeah. So how does this look in code? First of all, we load the image. We create a CI filter with the contents of that URL. And then I go and I query the default white balance settings for that image, which typically a camera will report the white balance information, what it thought it was when the picture was taken. And that's how you get the default settings, which are not always right. Then later on, you can go and tweak it with changing the exposure.

This is my filter, and I set it to the value of whatever the sender was, either the text field or the slider. The same applies for the white balance. I just create a CI vector with X and Y. I'll get to that a little bit later. And I set it on the image. There's also a way to go and click, which is also shown in this example. And I'm not going to get too much into that.

In the UI, you have seen two ways of setting the white balance. There is the clicking, and there's the temp/tense slider. That's the two things that most people are really familiar with. If you want a little bit more scientific approach, you can also set the X and Y color values, which is what we're actually using in this particular example. I think that's it for the reading demo. There's going to be another one later for writing.

So now that we've produced these great results on screen, the last thing we want to do is save images. And this can also be done with Image I/O. It's very simple. First of all, these are the general steps that are involved with saving images with Image I/O. The application needs to provide the framework with the image and optional metadata, and then also a couple other things, such as various options that are used when saving the image, such as compression type or compression quality.

They also specify what file format it should be saved as. Once these inputs are provided, then the file format code will compress the pixel data, embed the profile, and embed the metadata and produce a file. So this is how this is done in Image I/O with the CG Image Destination API. First off, there's a way of getting the list of what file formats are supported by Image I/O.

You can call CG Image Destination Copy Type Identifiers. Again, this is very important for your application to call. Not all formats that are readable by Image I/O are writable, and that list of writable formats is likely to improve in the future. So it's a great thing for your application to make use of this API.

Once you know that your file can be written, you can create a CG image destination type by calling CG image destination create with URL, create with data, or create with data consumer. At the time that you create the destination, you also specify the file format in the form of a UTI string. You specify the number of images that will be written into that file.

Next, for each image that you want to write into that file, which may be one or maybe more than one, you can set the image, options, and metadata all at the same time by calling CG Image Destination Add Image. Then when you're done, you call CG Image Destination Finalize.

So let me show you how that looks in code. Here's a function that's called WriteJPEGImage that does exactly what it says, which is to write a JPEG data into a URL with one small piece of metadata, in this case, which is the DPI of the file. First thing we do is we call CGImageDestinationCreateWithURL. We specify that the UTI is public.jpeg, and that will be providing one image.

The next thing we'll be doing is providing this dictionary. And again, the dictionary that we provide is used for two things. One is for metadata, and the other are options for saving. So in this case, we have the first property in the dictionary is CG Image Destination Compression Quality, which we're setting to 0.8. So the libjpg code will be passed the value from this dictionary.

The other values in this dictionary are metadata properties, such as in this case, we're going to be specifying the DPI width and DPI height based on the input parameter. So given this combined dictionary, we will call CG Image Destination Add Image, and specifying the image destination, the image, and the options and properties that we specified. Lastly, we call CG Image Destination Finalize.

Here's a similar example for TIFFs, slightly more complicated because there's some TIFF-specific attributes that we need to set. Again, we call cgimage-destination create with URL, this time specifying public.TIFF. And what we want to do is we want to specify a TIFF compression type. And in this code example, what we want to do is if the bits per sample is greater than 8, we want to use no compression. And if it's less than or equal to 8, then we'll use LZW compression. So the magic values for this are 1 and 5. You could include the libTIFF headers and get these values, or define your own constants.

The important thing to remember is you don't want to specify LZW if it's greater than 8, because you'll actually produce files that are slower and bigger. It's unfortunate, but true. So we put this property inside a dictionary which we're calling the TIFF properties. This is a sub-dictionary that we're going to be putting these TIFF attributes in. Then we're going to be creating the main property dictionary, which is going to contain, as before, the DPI height and width and the TIFF dictionary that we just created.

We then pass this combined dictionary to CG Image Destination Add Image, specifying the image and the destination in this hierarchical dictionary of metadata and options. And then we call CG Image Destination Finalize. So one thing you might have noticed is that we weren't doing anything here to preserve metadata. Ideally your application, if it reads an image, should make note of the original metadata. And then when it comes time to write, start with that metadata, and then specify the options on top of that.

So that code wasn't shown. Also you'll notice from this code that there is stuff that needs to be done that's file format specific. It should be expected. File formats have different features, and you need to be able to take advantage of those features. So there's a little bit of work on the case of an application if they want to know how to write TIFF files. There's stuff they need to know about TIFFs, and if they need to know about JPEG files, they need to know about JPEG files.

This can be made a lot easier, however, given a new feature we have in ImageKit, which provides an accessory view to an NSSavePanel. And the idea behind this is to make a lot of this work and drudgery very, very simple for a typical application that just wants to put up a save panel. So to show this in action, again, I'll be bringing up Stan, who will show this.

Sounds like this time the microphone works. So let me go back to my demo. And let's say we have made our image adjusted to taste. The white balance here is a little bit off. This is in nearby Santa Clara, south of here. I live there and I could not believe my eyes.

So I now just adjusted this image to taste, and I want to save it. Now, you saw the dictionaries that David was showing. And that is pretty complex about all the settings that you need to put into those dictionaries. You need to know a lot of stuff about public.jpeg and all that.

And this image kit, new image kit feature, is this accessory view that can be added to your save panel pretty much automatically. And now I can go and I say, for instance, I want to save this image. I'm going to save this image as jpeg. And you see automatically, I did not have to write any code for that. The secondary view, when I go switch again, you see it changes, has come with the appropriate further controls for that given format. So I can now save the image as best. Let's say Facility 1. Personally, I don't like hide extension. So now it goes and saves it.

And I will go and-- First I did a save as, so let me load the raw image again and save that. Again as JPEG, this time as low-quality Facility 2. And when we go to the finder, Desktop demo. Here I should have-- this one is 3 megabytes, and that one is 144 kilobytes. So you see that the slider immediately took effect. And I'll show you the code behind it, that there really wasn't one of that.

[Transcript missing]

The first step, however, Image I/O saves images, CG images, and for all the adjustments, we had a CI image because we're using a CI filter or a series of CI filters. So in our demo application, we also have a utility method, CG image, that converts the current CI image that's being shown to the user into a CG image. There's a number of ways of doing this.

This is one of those. Basically, I use an sRGB format. All of my files in this particular demo are with the embedded sRGB profile. And I render that image into a bitmap, and then I can save that CG image that was represented by that bitmap. This is necessary because a CI image doesn't really exist until you render it.

At this time, I would like to use the opportunity to point out another thing that David has mentioned before, which has to do with color profiles. It's actually quite frightening how many people don't use color profiles every single time when they draw something or when they save an image.

It's really important to do that, when you go and surf the web, especially when you're on a Macintosh. A lot of people post pictures from a PC without embedding a profile, and then on the Mac, which is a different default gamma value, it looks kind of washed out.

And this will be getting more and more important to maintain that you always have a profile embedded so that the computer knows how to interpret those bits. And in order to ensure that you can find out if your application is behaving correctly, that all of its drawing is done with color matching, we have added a new feature to Quartz Debug, which helps with that particular problem. I opened the-- window listing Quartz Debug. And here I can go and select my application.

And when I right click, you get a pop-up that allows me to turn on one or both of the different warnings that the system can issue for you, for a device profile warning and a screen profile warning. If you load an image into your computer without an embedded profile, it will be by default get the device profile, which is-- dependent from device to device. And if you turn on that warning, any time that that image is being drawn, it will be drawn in a very funny way. We invert some channels and stuff like that to make it visible.

Similarly, sometimes the application will load untagged images and convert them to the screen profile. And you can turn on the screen profile warning as well. On the other hand, the screen profile warning might not be always appropriate because you might have-- just pre-profiled it for faster rendering for the screen so that at the time that the image is being rendered, it doesn't take up any more time.

So we keep those two separate. Now what's going to happen if you turn it on? Currently we have a bug in the seed that you received that it actually turns it on for the whole system, not just for that one application that you chose. So the whole system is probably going to go a little bit haywire.

What happens is you see this image did not get inverted or in any other way. It's just dramatically changed. That has to do with the fact that our sample application treats color profiles correctly. But I would encourage you to try that feature on your own imaging application, or any application for that matter, because this will be getting an increasingly bigger problem.

And you might find, for instance, Cocoa Widgets turning purple and stuff like that. We're still working on our bugs. But make sure that your components of the UI work correctly in this sense. And with that, back to David. DAVID CHANDLER: Thank you. So that was the last thing we wanted to talk about today.