Graphics and Media • 1:12:10
Make your Carbon application's graphics shine by moving them from QuickDraw to the modern, powerful Quartz 2D graphics engine. Bring your laptop to this hands-on session and learn techniques to convert your existing application drawing code over to Quartz 2D. You'll also learn how to take advantage of Quartz 2D's extensive capabilities to deliver enhanced functionality. This is a must-attend session for all Carbon application developers with QuickDraw dependencies. Learn how to move beyond gWorlds, CopyBits, and VisRgns to a whole new world of modern 2D graphics functionality.
Speakers: Joseph Maurer, Ted Jucevic
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning, everybody, and thank you so much for getting up early and coming to this session. You know, I didn't know quite what to expect. It was announced as a hands-on session, but I was worried it was going to be a hangover session. My name is supposedly on the screen together with a nickname. And please be careful with pronouncing the nickname. Yesterday evening, I overheard somebody talk about the Quartz Barbarian. That's not good.
Let's get right into it. We have something for everybody to know that's not quite true. We have a restricted audience, as you can see by looking around you, which means that nearly everybody has already moved to Quartz, and there is no need for me to stand up and to come back anymore next year. So, Carbon developers who still have not Finish the move to replace QuickDraw by Quartz.
There's one serious thing, among all the jokes, that I want to point out. We really, really recommend that in this process you also consider adopting the HIVU architecture. I know many of you have made the step already, and those who did will agree with me that it's a real good recommendation. It will make your code so much cleaner, and it's just a natural environment. To adopt the Quartz API and use it.
By something for everybody, I meant that we start out really simple with some hand-holding for those of you who should have never started getting their feet wet in the Quartz API or their hands dirty, if I dare say so. So this is going, and that's going to be the just draw step.
But then we advance fast, and I will bring up Ted Jucevic from DTS on stage. He is the DTS doctor who deals with your incidents, and he knows the answers to that. He knows your questions, and he presents his answers in a sample application called Carbon CG Compositor. And I will come back with, under the theme, mouse tracking, where I want to cover a lot of things.
So, we have a lot of background, and mouse tracking is just the theme that holds it all together. So, how about this just draw? The idea was to make it as simple, as basic as possible, and to just have to get the start, to try out. Main points, we show how to get the CG context. We work with the HIVU coordinate system and have to spend some time explaining around with coordinates.
And then, the first not completely trivial step is how to replace a drawstring. And I think everybody who moves from the QuickDraw API to Quartz sooner or later runs into this need, and we need to spend some time on it. How about the coordinates? Well, we have the left-handed system and the right-handed system. The origin either at the top left, as we have been used to by QuickDraw, or the bottom right. The pictures below show how to move from one to the other.
The X coordinate doesn't change the horizontal coordinate, and the Y needs to be flipped and shifted. In Quartz parlance, this means we apply an affine transform to scale the Y axis by minus one. It flips it up, but then we have to shift it back down. Otherwise, you wouldn't see anything in your view, which is shaded, where the drawing happens. The good news is that it doesn't matter if you go from QuickDraw coordinates to Quartz, native Quartz coordinates, or back.
It's always the same transform. And instead of building it up by transform scale and then a transform translate, we just will make a simple transform that contains everything. So, just draw as simple as possible. The only thing is a piece of diagonal line so we know where the origin is and in which direction. The Y is oriented, and then a drawstring.
So, we have some real work to do. Let's go. Can we move to the Xcode machine, please? And I certainly wish I wouldn't have to turn my back to you during all this work. So this is one of the two examples that you find in the package that you downloaded, and you're supposed to work along with me on your laptops, on your powerbooks. I have two variants, the QuikTrav variant and the Core graphics variant. Let's look at it.
I promised it to be as simple as possible. If you ask Xcode to come up with a Carbon application, then that's what you get. And the only thing I added was installing a draw handler on the Windows content view. This is something the HIToolbox people frown upon a little bit.
It's not really the real way to deal with HIViews, but it's for now the quick and dirt the shortest way to get something going. And afterwards, I will... Okay. I'll fix this and create a correct HIView to be embedded in the content view. So what we're interested in is the draw handler.
That gets passed in. And by the way, note that the view, as usual here, gets passed in as user data, such that when our draw handler gets called, Then we have the user data that contain our view in case we need to get back to it, and we may have to.
The first thing I'm doing here is pulling out the CG context, which is one of the event parameters, and then pass it along to my just draw something just to factor it out while we are tinkering around. And our just draw something for now doesn't use the CG context ref yet because I only have the QuickDraw version. But now we are getting to work and we are replacing this by Quartz. Move to, that's an easy one. CG context, move to, point. Line 2, that's an easy one. CG Context, add line to point.
Okay. Well, we are still real beginners, so we want to see right away what happens. Oh, this worked well. The part that worked well was that we don't use the QuickDraw code anymore. What we are missing here is that the model is different. In order to get something to draw, we need to tell what we want to do with this path that's current inside the CG context. We can either, and we just, so far we only built the path, but we didn't do anything with it. We need to ask the context to stroke the path, for example.
Maybe this will work better. And now we have a nice anti-alias diagonal line, just as we expected. Next step, the drawstring part. While we have text font and text size, in Quartz this is called CG Context Select Font, and I don't remember the parameters. So, yeah, I could have remembered the context, and then we need a C string as font name and the size. We can do that. Oh, here it comes. Okay.
Let's try Geneva. And Aten, just to match it. Oh, and the text encoding thing. KCG. Is there something? No. Encoding. Oh, yeah. Encoding Macroman. That fits the bill. And in order to show text, we don't draw the text or stroke or whatever. We need to show it. Show text. Now here we have two alternatives.
Show text or show text at point. It's probably safer to take the at point. The background story is that QuickDraw maintains the current pen position and uses it also for the next text drawing call. Whereas in Quartz, we just had built a path internally, stroke it, now it's gone. And we haven't any pen position for text drawing. So we better specify it. I forgot again the parameters. No.
I meant text at point. Hitting the wrong keys all the time. And our string, ha. Now this time we need to set it up because I use hello world as Pascal string. So that's where we are with the beginning of the characters, and this, the length is the first byte. OK. Build and go. I did something wrong again.
Much better. Well, not quite finished yet. So this is the story about the coordinates. If you remember the little drawing with the coordinate flipping and drawing, there was a little character drawn. And flipping, of course, put the text upside down. And that's what we are running into now.
[Transcript missing]
There is a call, "HIV, get bounce." And now remember that I said, "HIV, ref." That's the user data. And here I get bounce, hopefully. And now I use CG Context Concat CTM, the current transform matrix, with a CG-affined transform that represents The combination of flipping across the vertical coordinates and shifting down. Okay, transform make. One for the X. These are the diagonal elements. Minus one for the Y scale. We don't move in the X direction, but we do move in the Y direction by the height of the bounds.
Okay, maybe this builds and runs. It does. And the text is, as expected, in the right orientation. But now, of course, the coordinates are wrong. So we could fix up all these coordinates such that we get back to the appearance in our QuickDraw content and still have the correct text. But in case you don't want to do this, we have another alternative. And that's the point.
[Transcript missing]
"The Y-axis." Let's see what happens now. So I didn't change anything in the drawing above. We always go to 0, 0, 100, 100. We still use-- The same coordinates as in QuickDraw, we still use the HIView coordinates, but we added instead this flipped text matrix. And now we should see when we can compile.
And if... okay. If everything was as simple, now we got what we wanted. We should go back to the slides now, so I get more to talk instead of screwing up code. The drawstring is missing. Well, it's not really missing anymore because I showed you how to do it with CG Context, Show Text, and so on. But you remember there is this MacRoman encoding. If I put in any other constant, you get garbage. It just won't work. Which means this Show Texted point is the most convenient replacement for drawstring in those cases where MacRoman is just about good enough.
As soon as you go abroad and you need other text systems or anything, or you have other requirements that go beyond the most basic MacRoman text drawing, that's not good anymore. So the next recommendation is the HITimDraw text box. And in the next phase of the next step of our Just Draw sample, I have put in the code that shows how to use it.
The good news is HITimDraw text box has an extra parameter. It's called orientation that allows you to use it in both coordinate system orientations. It makes it convenient. The bad news is you cannot completely control... Well, you can, and that's what I'm doing actually in the code, but with bad consciousness, with embarrassment.
Because the only way to get the font and size that you really want is to specify it through a graph board. So that's on the other side. The benefit is you have symbolic virtual font ID values in HITimDraw text box, which are the right way to go for the completely high resolution screen future and so on.
And to be completely in line with the general look and feel of the UI if somehow the theme changes. And then finally, you know, ATSUI is the right... This is the gold standard of doing text on the Mac OS. You get everything. And if you look at the last line, you will understand why I'm emphasizing so much the coordinate flipping all in back. Atsui expects you to use a coordinate system that is native to Quartz that has the origin at the bottom left. We should go back and look at it in code. This will be faster now.
It's, of course, prepared already. So I just walk you through a little bit. And I have a little bit more bells and whistles in here. You see we want to flip between various ways of drawing text, either the CG context show text or the HI theme or the ATSUI mode. And then we flip back and forth between using HI view coordinates or not.
And a global that's being set in a little key handler that I added. Depending on where I hit the keyboard, we select one or the other. And then when we just draw something from before, it gets called in the same way. But if we decided not to use HIVU coordinates, then we do this.
This additional transform on the CTM to play everything, all our drawing in the native. And then there is some additional thing which you will find in the comments and we don't have time here to go through everything. I just run it and... through. Now the extra thing is that the title bar shows the current settings. Here we have show text with HIV coordinates. I can flip back to the draw string and forth and you see that the text is not being rendered in precisely the same way and that the diagonal line is either anti-aliased or not. Okay.
And then you can flip through and use hithin draw text with or without, sorry, with or without the HIV coordinates and there's no change. Why is there no change? Well, because I spent hours and hours to try to get it right. And that's it for me. The overview at the beginning was about handing it off to Ted Jucevic now. So please welcome Ted.
Where is he? Thank you, Joseph. And you're going to need slides, right? Let's go back to the slides real quick. All right, so my name is Ted Jusovic. I'm one of the DTS engineers, as Joseph described, and I've had actually quite a few questions from people transitioning from QuickDraw to Quartz.
There I am. One of the things I've found is a lot of people are trying to do it incrementally, and it seems like what people are actually trying to do is they're going first to Quartz and then they're moving their code to HIV use. And one of the things I've found is it's actually easier if you first transition to HIV use because then your coordinates are still in the same area, you know, it's all real easy that way. And then you make your transition to Quartz if you're going to do it incrementally or just do it all at once and just save yourself the hassle of going partially.
So what I'm going to be showing is the Carbon CG compositor. It's a sample that's kind of a compilation of a bunch of DTS incidents that I've gotten. Essentially, I drag in some images, adjust the image translucency, maybe select a masking color, and so like I'll select a pixel and it'll mask out all the pixels that have a color that's similar to that one. And then I can lay images on top of each other and then drag out the results to the finder.
So what is this going to show us? Well, I'm using cgImageSource and cgImageDestination to bring image files in and save them out. Cool thing about this is it works really well with the Pasteboard APIs. They both use MIME types, and it also makes it easy for me to just drag in any image source and not have to care about it. As long as cgImageSource supports it, I automatically get it for free.
And down the road, when more image sources are actually added, I'll get those for free. The same thing when I'm dragging out to the system. All I have to do is specify a MIME type and a file name, and it'll convert my images automatically to that image format.
We're going to talk about adjusting the translucency. There's two kinds of translucency that we're going to be talking about. One is the alpha of the entire image, and so I'll be adjusting that with a slider and then compositing that over other images. And then also creating translucency by creating an image with masking colors.
And then I'm also going to talk about color spaces. The really cool thing about color spaces is it makes color matching just easy. It does all the color sync for you. Whenever you're drawing in Quartz, your colors and your destination have a color profile that go with them. And so if, say, you're drawing with a color that's different from the color profile that you're drawing to, it will actually do the conversion for you.
The one thing, though, is you want to stay away from device color spaces because those essentially mean that you know better than the system what colors you need, and you'll actually, most likely, you'll end up with colors that you're not expecting. Going back to the image source and image destination, you don't have to use QuickTime for that.
And then also with the alpha, Alpha is easy with Quartz. You essentially get it for free and almost have to just be aware of it. I'm also going to talk about using some off-screen drawing spaces. I'm using CG bitmap context. One thing to be aware of is that when you're using an off-screen bitmap like a CG context, its orientation is different from the current HIVU orientation. So if you're going to be compositing that into your HIVU, you have to do the flip like Joseph talked about.
I'm also going to be talking about creating images from it. That's essentially like the copy bits, and then using the context to scale the images if needed, and create ones with the coordinates I want. It turns out that I actually do have to use a region. I get back from HIVU, I get a shape, an HI shape, that I have to convert into a region to show the drag highlight.
So there's a few APIs that, while they're deprecated, you still have to use. But if you've noticed in Tiger, there's a lot less of those APIs. And then going forward, we'll even have less of those APIs that you still have to use, even though they're still deprecated. And then I'm just going to talk briefly about premultiplication.
If you create a CG bitmap context, you own the bits with it. This is an off-screen bitmap context. But one thing to be aware of is if your bitmap context has alpha in it, and it's not just a mask, those bits will be premultiplied alpha. And so if you want to get to those bits, you're going to have to un-premultiply them before you manipulate them and then premultiply them back.
And I have a function that I can show you that does exactly that. So let's actually look at this sample. And you'll have to get the sample after-- it'll be available on our web sometime next week. It didn't quite make the big drop. So actually, if we could switch to the other demo machine, demo 2.
So let me just actually show you what I'm talking about. So this is my application. It's a simple application for dragging stuff in. And let me just drag in-- I'm going to show you an image. This is Times Square, the Rockefeller Center. And let me just select a masking color. So essentially, I just selected a pixel, and it decided that anything that was similar to that pixel, and I set up a kind of a masking range, it went and made essentially translucent.
But I actually want the full I have a picture for my background, so let me drag in a different one. Let me paint this one. And that composited to the background. And at the same time, I'm compositing this to what I'm calling is my scratch pad, which is my off-screen bitmap context. I'm kind of layering stuff, too. So let me bring in the original picture.
And you can see over here, I got this slider to adjust the translucency of the picture that I'm overlaying. And that's actually-- it's real-- like I said, it's real easy in Quartz. You get that kind of stuff for free. And then I can go ahead and select, let's say-- And you can see the water from the other image showing through. And Quartz is mostly doing this all for me for free.
I don't have to go through like you guys did with QuickDraw and kind of simulate alpha in that unused area of a 32-bit pixel. So it makes it really easy. And then, of course, now I've created an image, dragging it out. And I don't know if you can see it. I actually have a border around this image that I used when I drew this HIVU.
You really can't see it on the stage. But I'm actually going to remove that. And the reason is because I've drawn the border in the HIVU. And when I'm creating my drag image, I'm asking the HIVU for its image. And so it turns out that border is coming out along with my drag image. And instead of that, what I'm going to do is create my drag image from my original source off-screen bitmap. So enough showing. Let's actually get to the code.
So, like I said, the first step, really, if you're going to do it incrementally, go to the HIVU stuff. And then if you're going to do it all at once, just go straight HIVU and... So let me look at my Draw Event Handler. And essentially, I've kind of organized things.
I've got my HIView files. These are the ones that have to do with HIView and events, event handling. And then I've got my drag-and-drop files that drag and drop, and then my CG files. And let's go to this Composite Images in View Draw Event Handler. It's a long name, but I'm trying to make it explicit exactly what it does.
This is something that's called by HIToolbox. The cool thing about this is I just tell HIToolbox, you need updating, and then it'll call my draw event handler, and I can draw my content. I stored in my window, using the set property, get property, my My bitmap context, my overlay picture, and then a mass picture.
And then I also store the alpha. And so I get those properties in my HIV draw, and I go down and-- Right here, you notice I have to actually flip the orientation because the bitmap context I'm pulling my image data out of is a CG bitmap context, so it has the inverted orientation from the normal HIVU. And then I come down here.
I composite my image to the on-screen context with the alpha that I've actually selected. The first thing-- so let me actually go-- And look at that function itself. And it's actually pretty straightforward. I saved my graphics state because when I apply an alpha to a graphics context, that "It's sticky.
But what you can do is use save state and restore state to So that you can actually draw the image with the alpha and then remove that alpha parameter from the context so that future images that you draw to that context won't be affected by that setting. And so right here, I set the alpha for the image that I want to draw in. I draw the image and then restore the state.
So in my-- when I'm doing some setup in the HIVU, I create my off-screen context. I have initialized Windows properties. So in my-- when I'm doing some setup in the HIVU, I create my off-screen context. I have initialized Windows properties. I have this function. Normally, it would return me a pointer to the memory because, you know, just like with an off-screen gWorld, you have-- you know, you allocate the memory in this case.
At some point, I'll have to free up that memory, just like with an off-screen gWorld. Nice thing about it is actually there's a function in here, and this is in my window closing handler, where I can actually-- Let's see, clean up the mask, clean up the overlay image.
I can get the data actually from the pointer to my data from the context. So if you don't have a place that you can actually save it off, you can get to it later and then free up the pointer or the memory that you allocated for the off-screen bitmap.
Let me just go in real quick and just point out the premultiplied code I have in here. This is just kind of as an example. I didn't need it for this sample, but I wanted to include it in. I'm assuming in my floats, the first is the red, green, blue, and then the alpha. One thing to be aware of is when you have lossy images and you're un-premultiplying, you know, they do a lossy data conversion.
And so while it's not technically legal, it's possible that a component value, say, like the red value, would actually have a value that's larger than my alpha. But the way you get to a premultiplied pixel is you multiply the component by the alpha. So if the alpha is, say, 0.5, even if the pixel for the red was 1, it's got to be 0.5 after it's premultiplied. And if it's not like that, I just go through and clean it up right here.
And then to un-premultiply, it's just dividing by the alpha. And you get your original RGB pixel values. And then you just save off your alpha for later if you want to premultiply. So let's go talk about the drag and drop and how easy the CG image source and image destination makes it for me.
I have my drag-receive handler, and this is something that HIToolbox, you know, it's very easy to set up. You can just set it up for your view, and it'll call your drag-receive handler. And I have this function create overlay image from pasteboard. And so what I'm doing in here is actually, since I'm dragging in files, All I'm looking for is this public file.url type. There's a constant also that goes along with it, but this one makes it kind of a little easier to see on stage.
And then I go down here and I have this function, create CG image from URL. So what I do is I get the URL that was dragged in and then I just say, create an image from that. Now, I don't know if that file actually has an image or not in it, but I figure if somebody dragged in, let's give it a try. And in my function, createCgImageFromURL, it's actually pretty simple and straightforward in how it works. Three sides lost it, though.
So I create my image source with the URL. And then I can check the status of that image source. And that will tell me if there's actually an image in it or not. If I actually look at the values, the other values I could get returned, essentially they're all errors.
So if that returns successfully, then I know for a fact that I actually have an image. And so I can just create my image. And all I'm asking for is the first one. In theory, an image file could have many different images in it. But I'm just asking for the first one.
And it's zero index space. And I just release my image source and return my image. And that will work with any image file type. So you don't have to go back to QuickTime to use it. You can just use CG image source. And you can load JPEG, TIFF, whatever I drag in.
So, and then conversely, when I'm going the other way, Let me get back to my sample. When I'm dragging out to the finder, I essentially create a promise. And then in my promise handler, I go and create. "The actual file that's going out. In this thing, I'm actually cheating a little bit because I have the file name in here. It's kind of hard coded, so I just kind of overwrite the original file. But All I do is I get from the pasteboard the location that the drop occurred to. And then down here, FS.
I get the URL graph. I'm missing my function call up here on the screen. Write bitmap context image of type to URL. So essentially what I'm doing is I'm taking the contents of my bitmap and writing it to a URL using the type JPEG. And actually, you saw I created the JPEG.
So just for kicks, let me actually just go and convert this to TIFF, and then let me go and take this type JPEG,
[Transcript missing]
Dun, dun, dun. Oh, and there's one more thing. I wanted to show you that HI, handle track drag. This is the region thing.
Essentially, when I get back the highlight that I've actually gotten-- The file I'm dragging in is a file that, you know, it's a file. And so it will be accepted by the drag. I get... From my view, I get my shape of the view. And I need this to show my drag highlight. Unfortunately, ShowDragHighlight only takes a region. So what I can do is convert that shape into a region and then call the old QuickDraw function. Let's see.
Oh, yeah, and then one other thing. I talked about when I'm dragging out to the Finder, I asked the system for-- The Views image, and it's actually right here. HIV, Create Offscreen Image. And that actually gets me the contents of the HIV on the screen and creates an image for me that I can then use as my drag image and stuff like that.
Alternatively, I can actually create my image All right, we've got new documentation, too. From my actual data source. And I have this-- it's kind of interesting. When you're setting your drag image with a CG image, it actually uses the bounds of the image, the actual pixel size of the image.
So if you remember, my context I created was 1024 x 1024. So if I just created a straight image from that and used that as my drag image, all of a sudden, I'd have this huge drag image coming out of this relatively small HIV. And so I use a function, createCgImageWithBounds from bitmap context that I use to essentially scale it down, but it could be used the same way to scale things up.
All I do is I create another bitmap context with the size of the image I want. And then I just render my image into that, create a new image from that bitmap, The temporary bitmap context, release my context, free my bitmap data, and then return. The image I created that has the new bounds and size.
It just makes it much better on the screen. So let me see. Let me make sure. I had a lot of things to cover, and so I wanted to actually write down some notes to make sure I didn't skip anything. And-- Right. So the last thing I wanted to talk about, and I'm actually going to just lightly cover this because Joseph is going to cover it in more detail.
is when I create my masking image, essentially what I have to do is I have to render it. Let me go down to the actual one. I have to do a little bit of math, you know, because my view has a limited size, and then my CG bitmap context I'm actually, you know, can hold my data is 1,024 by 1,024.
So I have to do a little bit of math to figure out exactly where in that bitmap context I have my data. And then I render it to a-- A bitmap context that doesn't have any alpha. The reason for this is you can't create a mask, an image with masking colors, from an image that has any kind of alpha, has a mask associated, or masking colors associated with it.
So, um, and then I have a fun-- once I create that bitmap context, I can, uh, get the pixel data from that bitmap context. And this is a pixel data for the actual pixel that I selected. I have a function that goes through and creates a mask, it kind of just creates a range based around those pixels because I'm actually using live pictures, and of course every pixel is just right next to each other. It's a little different in the real world.
They're not all the same. And then I just use a temporary image I created from that context, and I create my new image from that. And that's all you have to do to essentially mask out colors. And so you get the blue screen effect for free, and Quartz will provide that for you. So let's see, I went through, I made a couple changes. So let me just go ahead and do a build right here. And with any luck-- I didn't actually change anything I didn't expect to. And so now, let me just drag in this image.
Let me start with this one. Paint this one to the background, paint this one in, and now we got a little bit of water in Madison Square Gardens and some palm trees and stuff. Nothing they would expect. I can still do the translucency with that masked image. Paint it, and then when I drag it to my desktop, I created TIFF.
Now, the finder could be fooled because it could be a JPEG image with a TIFF extension because I changed the name of it. So just to make sure, let's bring it up in preview. And you will see this is actually a TIFF document. It's not just TIFF by name.
So with that, I've actually shown you my code. And these were the kind of issues that I've been running into in DTS. And again, let me just reiterate, make your transition to the HI Toolbox. It makes things much easier. And then do your transition to CG or do it all at once. And then You can get the features and the values from the CG for free. So let's go back to the slides real quick.
So like I said, drag and drop, CG image source and image destination for dragging images in and out makes it really easy, real nice, and work well with the Pasteboard. You don't have to use Quicktime again for that. Copying image data. You can just create images from bitmap contexts. If you wanted to just get a portion of say of an image, you can just adjust the transform so that when you draw into your bitmap context, you would actually just draw the portion of the image that you want. There's also subsampling.
"Color Management is easy. I didn't have to do anything with ColorSync in there. When ImageSource brought in my image, it actually figured out what the "The color space and the profile that went with it. And so even if I had multiple images from multiple profiles, the color correction is automatic for me." And so with that, let me hand this back to Joseph, and he'll finish it up with the mouse tracking demo. Thank you. Thank you, Ted.
Why mouse tracking? When I started thinking about a suitable subject for For a sample code for this session, I wanted something nostalgic that went back to '83, '84, something really QuickDrawish. And I remembered how fascinated those of us who were already born at that time. How fascinated we were to track the mouse and do freehand drawing. So that's what I wanted to do. First, I had to figure out how to do it in QuickDraw.
And we will look at it in a way. Then I found a reason why I needed to talk about it here because we are again juggling around with coordinates. The mouse tracking loop, the way it's set up, requires that we convert from global coordinates to local coordinates. And it's until Tiger, before Tiger, it wasn't quite obvious how to do that without using a QuickDraw call.
So here we are, and the HIPointConvert call is the one I want to bring to your attention. It's a tiger-only API, but it's really convenient, and it's the right thing to go about these things. Here is the mouse tracking loop, and we will encounter it in various contexts over the rest of the session.
I've wrapped the conversion function from the global QuickDraw point to the local HIPoint. I've wrapped it up just to take into account that we have a neat API solution in Tiger, but a not-so-neat one before. And you look at the bottom. Throughout the loop, we call HIPoSETNeedsDisplay, such while we are dragging. The HIVoSystem will just repaint everything as we need it.
So this loop here contains a polygon because our freehand drawing, I just collected in a polygon and redraw the polygon. That's the way to do it in QuickDraw. How would we replace this in Quartz? We have a CG path, and we better use a mutable path because we are changing it all the time. We are building it up on the fly.
I will show how to use such a CG path in practice. Now, this CG path thing is really fundamental for For many cases where you are wondering how do I replace this in Quartz, it replaces obviously things like polygons, but it replaces also regions, region handles. In many cases where the region is naturally represented by its outline. And in the other cases, we will work with image masks. Ted already has spent some time on this, and we will come back to this. It's a concept that's unavoidable for any non-trivial work. So, get started with house tracking. And I need the first machine again, please.
Again, this is the second portion you have on your sample code. And this time I have three variants. The QuickDraw variant, the first one, let's first see how it goes. Yeah, that's what we wanted. See? Now you can scribble around as much as you like and find nice things. Okay. So there is not much to talk about it because you have seen it on the slide already. Only one thing.
We replace the view by view data to have room for storing our private stuff, to hang it off of it. And this file here, the mouse tracking view, is now as views should be done. See, this in our main, it's again the main that you know as a default project in Xcode, but here, once I have created the window from the nib, I create a mouse tracking view. And this then is all done separately.
The next step, you already have had a sneak peek. I want to load an image that comes along. This is to replace the picked resources that we used to add to an application and then load the picked resource and draw a picture. So that's how we do it now.
I could have used a couple lines for image IO to make it even more flexible, but the idea is that these images, they're part of our main bundle, so we know them, we know them by name, and we know how to call. Here I have a PNG image. Thank you.
And in order, because we still have a lot of work to do, I promised to replace regions and to replace CopyBits and CopyDeepMask and all this stuff, I suggest we... You let me jump forward and avoid that I dabble around in the code again. So here we replace this polygon tracking and drawing by the CG path. You see I have made room for the image as a background thing to make it less boring. And I have now, instead of the polygon, a path. There's one thing I need to explain. There is the path applier. This is used here when I draw the view.
[Transcript missing]
and depending on one or the other images that would show up upside down as well, but you know that already. So once I want to get the path drawn, I don't have an API to, say, stroke the path. Because Quartz has a current path in the context, and that's the path that's used when I stroke it.
So I somehow need to put this path, which is an independent path, into the context. And the same thing, we want to be generic and universal such that I can do whatever I want, and my path applier only translates and carries the separate path over into the current path in the context. And then I can do with this path what I want. For example, stroke it. So the path applier Just gets, it gets called for each path element. We know our path is a polygon we only have move to and line to. And that's it. There's nothing else.
Should I-- oh, yeah. Here's all this boilerplate code. I decided not to use the HIV framework and to keep everything bare bones and to do all the legwork myself. So here you have all the boilerplate code. Each time you instantiate a view, you register the view, you tell it which events we are handling, and we have hit test and control track in addition to the drawing. You can also use the initialize function to create a new view. That's in the hand... That's where we set up the image, load image from main bundle, and then we keep it around. Okay, so this is supposed to work. I just picked any picture.
The picture is positioned at the bottom left here. And those with sharp eyes may have seen that the reason is that my compiled conditional is flipped to use CG coordinates. If I would, and that I, the destination rectangle, I always set it off the origin 0, 0. If I would flip this to the other coordinate system... Then, it would be at the top left. Now we still can drag around the mouse and do some scribbling, and this gives us an idea.
Okay, this, yeah, whatever. The next thing I want to do is put it in, paint the background. White is just too white. Okay, this, yeah, whatever. The next thing I want to do is put it in, paint the background. White is just too white. Okay, this, yeah, whatever. The next thing I want to do is put it in, paint the background.
[Transcript missing]
Okay, this, yeah, whatever. The next thing I want to do is put it in, paint the background. Here it comes. What's the color space name? KCG color space. Do we have one? Generic RGB. Xcode already knew what I wanted. And then we set CG context, set fill color space.
[Transcript missing]
So green is the dominant thing. And of course, it's completely arbitrary. It doesn't matter. Just want to see how it looks. Now, we are going ahead and drawing the picture again. Oh, here I simplified and only made it with the HIVU system. And then we have some other stuff here around.
[Transcript missing]
Yeah. So this was the point of introducing a masked image. You see, in many cases, what we want is we want to fill the whole background. We want to mask out the background of an image. And how would we do that in QuickDraw? This is one of those problems, one of those questions that has been asked over the last couple of years.
With QuickDraw, we would draw this picture into a one-bit off-screen. Well, that wouldn't give the right result the way the colors are mapped to black and white, so we would install a search block and then be able to deal with each pixel with each color and decide how to map it to black and white. And then on this off-screen bitmap, we would call bitmap to region, and the region is used as masked. So how would we replace this in Quartz? And that's the whole point here.
We need to build a masked image. And this masked image... is being built, of course, by Create Masked Image. Now I have two variants. It's a repetition in some way of what Ted already used. It bears repetition in my mind because you are going to use it in practice sooner or later.
The idea is to mimic the QuickDraw thing, but we don't have one bit off-screen, so we go into a gray level off-screen. This is the bit, the gray level off-screen. Bitmap context, we draw the picture into it and create an image off of it. Now here is some mystery call. Let's remove it for the time being. And the masked image gets produced by the call create, CG image create with mask. So if we run this, Did I set it back to drawing the mask? Yeah, I did.
And Peter, unfortunately, has a color that looks like he's squeezing in the stomach. So what is going on? Of course, our mapping of our drawing of the image into a gray level off-screen produces a transparency corresponding to the lightness of the image data. And we need to fix this.
And the fix is here. We need to fix the opaqueness. So this is the equivalent to what we would have done in a search proc. We can go through all the pixels of this 8-bit bitmap off-screen and define a cutoff, heuristically. And as soon as the cutoff is below a certain-- as soon as the pixel value, the gray level, is below a certain value, we just make it completely dark and cut it off. And with that, Peter looks good again, and we have masked out the image and have a common background. For a change, we should switch back to slides so I can talk to slides for a while.
So what did we learn? Replace picked resources, image create with data provider, and internally it already goes through ImageIO anyway. It has been factored out, so it's all up to you. And the whole talk is about replacing moving from QuickDraw to Quartz. Yeah, notice that we talk about moving now and not about transitioning anymore. The word transition has been completely used up on Monday in the keynote.
Yeah, so we are moving and replacing all the regions and all the CopyBits things. And I forgot to show you how we come back to this, how this old-fashioned type of creating a masked image by going through a gray level off-screen and then creating the masked image, we could have replaced it by a single API call with masking colors.
And this is the call that Ted already used, too. Now, these are Tiger APIs. Before, it's feasible, but you need, again, go through a bitmap context and then walk the alpha manually into the alpha channel to mimic the mask thing. But there was a good reason to introduce these APIs, at least in Tiger.
So, the next step. We are getting into more copy mask things and copy deep mask in particular. And here's an overview of what we want to do. The CopyBits killer is CG Context Draw Image. Instead of source pixels, we use the source pixels of the image, which are provided by the image's data provider.
We are going to do fancy things now, which don't even need overlay windows. I should not forget to show you again where we do a hit test. So, back to my machine here. You know, we forgot that we can still scribble around here. And at this point, I had an idea and I said, maybe I should show how to do this. That's a really QuickDraw thing.
See? How would you do this in QuickDraw? You would need a couple gWorlds off-screen, CopyBits back and forth, and then use CopyDeepMask to a hole in the belly. To get the transparent effect while we are dragging. This would be a lot of work to show. And it's something really, in my mind, QuickDraw-ish. We cannot translate the QuickDraw API calls into making a Quartz version of that. We need to start out completely differently. And it's already all there. So the first thing is do the hit test.
We have two states in this application as it stands now. One state is we are tracking the mouse to draw the outline. Then we are done drawing the outline. And as soon as we get into a hit test that we clicked inside this outline, we are in the second stage where we are dragging around the image.
So this needs to be reflected after the hit test. Hit testing means we are only interested in the hit test when we are done with building our path. And now we create this one by one, a single pixel bitmap context. This single-pixel bitmap context gets moved to our rear point.
"Where we want to test the hit. Now our path that we remember, we apply the path such that it, this time we pass in our bitmap context of course. The bitmap context now has our path as current path, and we fill it. When we fill this path, we check, did this touch our one-pixel bitmap context? If it did, then we know our point was inside this path.
And then we release things and we return the result in the field hitSelection. And with that, as soon as we are in this hit selection, we go to the tracking. And this time we have two tracking loops depending on whether we hit the selection already or not. The first one we discussed already. The second is drag the image selection around.
And it's just a copy-paste of the original mouse tracking loop instead. But instead of redrawing the path, this time we just keep up-to-date the selection offset, which I added. That's where we dragged it to. And say we need to redraw now at the new offset. So it's all inside the draw the view.
Each time we draw the view, we fill the background. We draw the image first into its original place where it belongs. And now we check, did we hit the selection already? There's one thing I want to do. I want to paint the selection now white, the original selection where it was, to suggest that we remove all these pixels here. OK, let's fill it with white. But in order to do so, I need to clip to my path. And again, you see that it's always the same mechanism. I use my path, which is kept in my local data.
I've applied a path to move it into the context, and now I can just clip to it. I fill it, but because I clipped, I don't even remember what I meant. Oh, yeah, that's the-- it's OK. So because I clipped and I want to undo the clip, I need to save and restore the G state around it. And now we keep going in our drawing routine.
Well, if we did not hit the selection yet, then we have a path, then we apply the path and stroke it. This is the first state of scribbling around. But if we are done with path building, Then, we first change the coordinate system such that we move it to where our selection offset is.
Again, once more, we need to apply the path because we need to clip to this path now to draw our image. Well, and now we draw the image, except that we want to apply an alpha to suggest we are not done yet with moving it around and to see through. And then we draw the image.
This is pretty simple for what it does. If you think about it, how this would work in QuickDraw, how you would have to do really much more work. It was a surprise to myself. Is there something else I forgot to point out? No, it's always the same, and you will find if you compare the various stages of the files, how they grow out of each other, how they repeat the same code, and how everything is pretty minimal.
With that, I'm ready to go back to my very last slide. Can we move? Yeah. So here's the summary. Honestly, HIV use are the way to go. If we can do anything to encourage you, and you need some encouragement, let us know. You are convinced now that you need to get used to the transforms and to juggling around with coordinates.
That's something new, that's something unavoidable, and it may take a while and a couple of frustrations, but it's not so bad as it may appear the first time you don't get it right. CGPathRefs, you need that to replace polygons and to replace many, many regions in your code.
There is really no need for CopyBits and CopyDeepMask. We can, in an example like this, You should be convinced that this is a good thing. So, looks like that's really the time to get rid of QuickDraw, and it's fun, and it will be really profitable for you, too. One more thing. I didn't talk about Carbon Sketch, but I have been working on it, and I will continue to work on it.
It's, of course, now HIVU-based. It has a couple of new features, and I will add some more. And within the next weeks, I will be done with it. And then it will take its time to go through the process of being posted, but please watch out for it. It's there, and it's growing.
That's it from my side. Here are related sessions. There's not much left anymore. Right after this session in 15 minutes or 20, optimal application graphics, which is optimization of usage of the Quartz API. And then please come to the lab and chat with me, bring some food at noon. and there is also the lab session for optimizing. And everybody knows Travis. That's where all your feedback should go, all your requests, all your complaints, and all your compliments. Thank you.