22

I have an image with a yellow vase in the foreground and transparent background:

enter image description here

I'm drawing it on a CGContext:

CGContextDrawImage(context, CGRectMake(0, 0, 100, 100), myImage.CGImage);

I can draw a shadow around it by using the following statement before CGContextDrawImage:

CGContextSetShadowWithColor(context, CGSizeMake(0,0), 5, [UIColor blueColor].CGColor);

enter image description here

But I want to put a stroke around the image, so that it'll looks like following:

enter image description here

If I did this:

CGContextSetRGBStrokeColor(shadowContext, 0.0f, 0.0f, 1.0f, 1.0f);
CGContextSetLineWidth(shadowContext, 5);
CGContextStrokeRect(shadowContext, CGRectMake(0, 0, 100, 100));

It (obviously) draws a rectangular border around the whole iamge like this:

enter image description here

Which is not what I need.

But what's the best way to draw the border as in the third image?

Please note that it's not possible to use UIImageView in this case, so using the properties of CALayer of UIImageView is not applicable.

Sanjay Chaudhry
  • 3,181
  • 1
  • 22
  • 31
  • Are you calling `CGContextSetShadowWithColor` before the call to `CGContextDrawImage`? – Ariel Feb 20 '13 at 01:22
  • @elcanibal: Yes. Otherwise, there'd be no shadow. I'm editing the original question to reflect that. – Sanjay Chaudhry Feb 20 '13 at 14:50
  • Please post a PNG of the yellow vase with transparent background. Your first (unadorned) image has a gray background. – rob mayoff Feb 20 '13 at 20:49
  • I've replaced the first image. It now has a transparent background. Please note that you can experiment with any image with transparent bg. I won't be using the this vase image in the app. It was randomly picked up off Web. – Sanjay Chaudhry Feb 20 '13 at 22:39

2 Answers2

16

One way to do this is to use the mathematical morphology operator of dilation to "grow" the alpha channel of the image outward, then use the resulting grayscale image as a mask to simulate a stroke. By filling the dilated mask, then drawing the main image on top, you get the effect of a stroke. I've created a demo showing this effect, available on Github here: https://github.com/warrenm/Morphology (all source is MIT licensed, should it prove useful to you).

And here's a screenshot of it in action:

enter image description here

Note that this is staggeringly slow (dilation requires iteration of a kernel over every pixel), so you should pick a stroke width and precompute the mask image for each of your source images in advance.

warrenm
  • 31,094
  • 6
  • 92
  • 116
  • 1
    I wonder how feasible it would be to port this to the Core Image Kernel Language. Such an implementation could be much faster than a straight loop with C scalar operations. – Peter Hosey Feb 21 '13 at 19:49
  • For a person with some experience with CI or even GLSL it would be pretty trivial. It's just a minimization operator over a neighborhood. It's my understanding, though, that Core Image is not pluggable on iOS. Additionally, if one were clever, one could probably take advantage of a separability or decomposability property of the kernel to do less work. – warrenm Feb 21 '13 at 19:56
  • Dang, you're right. I hadn't noticed that before: CIKernel is not present in iOS (going by its absence from the iOS docs). – Peter Hosey Feb 21 '13 at 19:58
  • 1
    Good news, though: @BradLarson has already done the hard work of writing the basic morphology operations as part of his `GPUImage` framework (https://github.com/BradLarson/GPUImage). These could surely be adapted to get the desired effect, with all the speed of GLSL. – warrenm Feb 21 '13 at 20:01
0

I would try either setting the stroke color and line width before your call to CGContextDrawImage, or tweaking the shadow (opacity, blur, etc) so that it looks like a stroke around the image. Let me know if this works!

Ariel
  • 549
  • 3
  • 10
  • That won't work. Two reasons: 1. Shadow doesn't simulate a stroke accurately. 2. I'll need to put both stroke and shadow. So I need to be able to really stroke the image. – Sanjay Chaudhry Feb 20 '13 at 22:41
  • You can set both those things before calling `CGContextDrawImage`, i.e. call `CGContextSetShadowWithColor`, `CGContextSetRGBStrokeColor`, and `CGContextSetLineWidth` beforehand. – Ariel Feb 20 '13 at 22:51
  • 1
    @elcanibal: But what do you stroke? You need a path. – Peter Hosey Feb 21 '13 at 01:09
  • Doesn't the image provide a path? I mean if core graphics can find a path to assign a shadow to, I assume it can find a path to stroke as well (it's the same...) – Ariel Feb 21 '13 at 18:12
  • 2
    @elcanibal: The image is just a raster of pixels. A path is constructed of `moveto`, `lineto`, `curveto`, etc. elements. Core Graphics doesn't provide a public API for tracing a raster to produce a path. – Peter Hosey Feb 21 '13 at 19:50