Cocoa Touch - Adding texture with overlay view - cocoa-touch

I have a set of tiles as UIViews that have a programmable background color, and each one
can be a different color. I want to add texture, like a side-lit bevel, to each one. Can this be done with an overlay view or by some other method?
I'm looking for suggestions that don't require a custom image file for each case.

This may help someone, although this was pieced together from other topics on SO.
To create a beveled tile image with an arbitrary color for normal and for retina display, I made a beveled image in photoshop and set the saturation to zero, making a grayscale image called tileBevel.png
I also created one for the retina display (tileBevel#2x.png)
Here is the code:
+ (UIImage*) createTileWithColor:(UIColor*)tileColor {
int pixelsHigh = 44;
int pixelsWide = 46;
UIImage *bottomImage;
if([UIScreen respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2.0) {
pixelsHigh *= 2;
pixelsWide *= 2;
bottomImage = [UIImage imageNamed:#"tileBevel#2x.png"];
}
else {
bottomImage = [UIImage imageNamed:#"tileBevel.png"];
}
CGImageRef theCGImage = NULL;
CGContextRef tileBitmapContext = NULL;
CGRect rectangle = CGRectMake(0,0,pixelsWide,pixelsHigh);
UIGraphicsBeginImageContext(rectangle.size);
[bottomImage drawInRect:rectangle];
tileBitmapContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(tileBitmapContext, kCGBlendModeOverlay);
CGContextSetFillColorWithColor(tileBitmapContext, tileColor.CGColor);
CGContextFillRect(tileBitmapContext, rectangle);
theCGImage=CGBitmapContextCreateImage(tileBitmapContext);
UIGraphicsEndImageContext();
return [UIImage imageWithCGImage:theCGImage];
}
This checks to see if the retina display is used, sizes the rectangle to draw in, picks the appropriate grayscale base image, set the blending mode to overlay, then draws a rectangle on top of the bottom image. All of this is done inside a graphics context bracketed by the BeginImageContext and EndImageContext calls. These set the current context needed by the UIImage drawRect: method. The Core Graphics functions need the context as a parameter, which is obtained by a call to get the current context.
And the result looks like this:

If you want to preserve the alpha channel of the source image, just add this to jim's code before the fill rect:
// Apply mask
CGContextTranslateCTM(tileBitmapContext, 0, rectangle.size.height);
CGContextScaleCTM(tileBitmapContext, 1.0f, -1.0f);
CGContextClipToMask(tileBitmapContext, rectangle, bottomImage.CGImage);

Swift 3 solution, essentially based on Jim's answer with Scriptease's addition, and some minor changes:
class func image(bottomImage: UIImage, topImage: UIImage, tileColor: UIColor) -> UIImage? {
let pixelsHigh: CGFloat = bottomImage.size.height
let pixelsWide: CGFloat = bottomImage.size.width
let rectangle = CGRect.init(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
UIGraphicsBeginImageContext(rectangle.size);
bottomImage.draw(in: rectangle)
if let tileBitmapContext = UIGraphicsGetCurrentContext() {
tileBitmapContext.setBlendMode(.overlay)
tileBitmapContext.setFillColor(tileColor.cgColor)
tileBitmapContext.scaleBy(x: 1.0, y: -1.0)
tileBitmapContext.clip(to: rectangle, mask: bottomImage.cgImage!)
tileBitmapContext.fill(rectangle)
let theCGImage = tileBitmapContext.makeImage()
UIGraphicsEndImageContext();
if let theImage = theCGImage {
return UIImage.init(cgImage: theImage)
}
}
return nil
}

Related

Translate detected rectangle from portrait CIImage to landscape CIImage

I am modifying code found here. In the code we are capturing video from the phone camera using AVCaptureSession and using CIDetector to detect a rectangle in the image feed. The feed has an image which is 640x842 (iphone5 in portrait). We then do an overlay on the image so the user can see the detected rectangle (actually it's a trapezoid most of the time).
When the user presses a button on the UI, we capture an image from the video and re-run the rectangle detection on this larger image (3264x2448) which as you can see is landscape. We then do a perspective transform on the detected rectangle and crop on the image.
This is working pretty well but the issue I have is say 1 out of 5 captures the detected rectangle on the larger image is different to the one detected (and presented to the user) from the smaller image. Even though I only capture when I detect the phone is (relatively) still, the final image then does not represent the rectangle the user expected.
To resolve this my idea is to use the coordinates of the originally captured rectangle and translate them to a rectangle on the captured still image. This is where I'm struggling.
I tried this with the detected rectangle:
CGFloat radians = -90 * (M_PI/180);
CGAffineTransform rotation = CGAffineTransformMakeRotation(radians);
CGRect rect = CGRectMake(detectedRect.bounds.origin.x, detectedRect.bounds.origin.y, detectedRect.bounds.size.width, detectedRect.bounds.size.height);
CGRect rotatedRect = CGRectApplyAffineTransform(rect, rotation);
So given a detected rect:
TopLeft: 88.213425, 632.31329
TopRight: 545.59302, 632.15546
BottomRight: 575.57819, 369.22321
BottomLeft: 49.973862, 369.40466
I now have this rotated rect:
origin = (x = 369.223206, y = -575.578186)
size = (width = 263.090088, height = 525.604309)
How do I translate the rotated rectangle coordinates in the smaller portrait image to coordinates to the 3264x2448 image?
Edit
Duh.. reading my own approach realised that creating a rectangle out of a trapezoid will not solve my problem!
Supporting code to detect the rectangle etc...
// In this method we detect a rect from the video feed and overlay
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer];
// image is 640x852 on iphone5
NSArray *rects = [[CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:#{CIDetectorAccuracy : CIDetectorAccuracyHigh}] featuresInImage:image];
CIRectangleFeature *detectedRect = rects[0];
// draw overlay on image code....
}
This is a summarized version of how the still image is obtained:
// code block to handle output from AVCaptureStillImageOutput
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
{
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
CIImage *enhancedImage = [[CIImage alloc] initWithData:imageData options:#{kCIImageColorSpace:[NSNull null]}];
imageData = nil;
CIRectangleFeature *rectangleFeature = [self getDetectedRect:[[self highAccuracyRectangleDetector] featuresInImage:enhancedImage]];
if (rectangleFeature) {
enhancedImage = [self correctPerspectiveForImage:enhancedImage withTopLeft:rectangleFeature.topLeft andTopRight:rectangleFeature.topRight andBottomRight:rectangleFeature.bottomRight andBottomLeft:rectangleFeature.bottomLeft];
}
}
Thank you.
I had the same issue while doing this kind of stuff.I have resolved it by doing below code in swift. Take a look if it can help you.
if let videoConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) {
stillImageOutput.captureStillImageAsynchronously(from: videoConnection) {
(imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
var img = UIImage(data: imageData!)!
let outputRect = self.previewLayer?.metadataOutputRectOfInterest(for: (self.previewLayer?.bounds)!)
let takenCGImage = img.cgImage
let width = (takenCGImage?.width)!
let height = (takenCGImage?.height)!
let cropRect = CGRect(x: (outputRect?.origin.x)! * CGFloat(width), y: (outputRect?.origin.y)! * CGFloat(height), width: (outputRect?.size.width)! * CGFloat(width), height: (outputRect?.size.height)! * CGFloat(height))
let cropCGImage = takenCGImage!.cropping(to: cropRect)
img = UIImage(cgImage: cropCGImage!, scale: 1, orientation: img.imageOrientation)
let cropViewController = TOCropViewController(image: self.cropToBounds(image: img))
cropViewController.delegate = self
self.navigationController?.pushViewController(cropViewController, animated: true)
}
}

UIImageView auto resizes when UIScrollView tapped

Ok, so I have a UIImageView inside UIScrollView. I dynamically load an image into UIImageView and then manually resize it. So far so good. But then, if I touch UIScroll scroll view (containing resized elements) everything is resized back to the original size. Just in case, here's the code that resizes the image:
/**
* Proportionally resize and position doodle
*/
- (void)resizeDoodle:(CGFloat)height {
self.doodleImageView.frame = CGRectMake(0, 0, self.view.bounds.size.width, height);
[self.scrollView setContentSize:self.doodleImageView.frame.size];
}
self.Imageview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
And This line keep image to fit
self.Imageview.contentMode = UIViewContentModeScaleAspectFit;
Write above code for restrict your issue.
Another way:
Use following method for specific hight and width (That you want) with image
+ (UIImage*)resizeImage:(UIImage*)image withWidth:(int)width withHeight:(int)height
{
CGSize newSize = CGSizeMake(width, height);
float widthRatio = newSize.width/image.size.width;
float heightRatio = newSize.height/image.size.height;
if(widthRatio > heightRatio)
{
newSize=CGSizeMake(image.size.width*heightRatio,image.size.height*heightRatio);
}
else
{
newSize=CGSizeMake(image.size.width*widthRatio,image.size.height*widthRatio);
}
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
This method return NewImage, with specific size that you specify :)
This code may be helpful for you:)

How can I make an UIImage programmatically?

This isn't what you probably thought it was to begin with. I know how to use UIImage's, but I now need to know how to create a "blank" UIImage using:
CGRect screenRect = [self.view bounds];
Well, those dimensions. Anyway, I want to know how I can create a UIImage with those dimensions colored all white. No actual images here.
Is this even possible? I am sure it is, but maybe I am wrong.
Edit
This needs to be a "white" image. Not a blank one. :)
You need to use CoreGraphics, as follows.
CGSize size = CGSizeMake(desiredWidth, desiredHeight);
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code creates a new CoreGraphics image context with the options passed as parameters; size, opaqueness, and scale. By passing 0 for scale, iOS automatically chooses the appropriate value for the current device.
Then, the context fill colour is set to [UIColor whiteColor]. Immediately, the canvas is then actually filled with that color, by using UIRectFill() and passing a rectangle which fills the canvas.
A UIImage is then created of the current context, and the context is closed. Therefore, the image variable contains a UIImage of the desired size, filled white.
Swift version:
extension UIImage {
static func emptyImage(with size: CGSize) -> UIImage? {
UIGraphicsBeginImageContext(size)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
If you want to draw just an empty image, you could use UIKit UIImageBeginImageContextWithOptions: method.
UIGraphicsBeginImageContext(CGSizeMake(width, height));
CGContextAddRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height)); // this may not be necessary
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code above assumes that you draw a image with a size of width x height. It adds rectangle into the graphics context, but it may not be necessary. Try it yourself. This is the way to go. :)
Or, if you want to create a snapshot of your current view you would type code like;
UIGraphicsBeginImageContext(CGSizeMake(self.view.size.width, self.view.size.height));
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Don't forget to include Quartz library if you use layer.
Based on #HixField and #Rudolf Adamkovič answer. here's an extension which returns an optional, which I believe is the correct way to do this (correct me if I'm wrong!)?
This extension allows you to create a an empty UIImage of what ever size you need (up to memory limit) with what ever fill color you want, which defaults to white, if you want the image to be the clear color you would use something like the following:
let size = CGSize(width: 32.0, height: 32.0)
if var image = UIImage.imageWithSize(size:size, UIColor.clear) {
//image was successfully created, do additional stuff with it here.
}
This is for swift 3.x:
extension UIImage {
static func imageWithSize(size : CGSize, color : UIColor = UIColor.white) -> UIImage? {
var image:UIImage? = nil
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.addRect(CGRect(origin: CGPoint.zero, size: size));
context.drawPath(using: .fill)
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext()
return image
}
}
Using the latest UIGraphics classes, and in swift, this looks like this (note the CGContextDrawPath that is missing in the answer from user1834305, this is the reason that is produces an transparant image) :
static func imageWithSize(size : CGSize, color : UIColor) -> UIImage {
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextAddRect(context, CGRect(x: 0, y: 0, width: size.width, height: size.height));
CGContextDrawPath(context, .Fill)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image
}
Same solution in C# for Xamarin.iOS :
UIGraphics.BeginImageContext(new CGSize(width, height));
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();

UIButton image rotation issue

I have a [UIButton buttonWithType:UIButtonTypeCustom] that has an image (or a background image - same problem) created by [UIImage imageWithContentsOfFile:] pointing to a JPG file taken by the camera and saved in the documents folder by the application.
If I define the image for UIControlStateNormal only, then when I touch the button the image gets darker as expected, but it also rotates either 90 degrees or 180 degrees. When I remove my finger it returns to normal.
This does not happen if I use the same image for UIControlStateHighlighted, but then I lose the touch indication (darker image).
This only happens with an image read from a file. It does not happen with [UIImage ImageNamed:].
I tried saving the file in PNG format rather than as JPG. In this case the image shows up in the wrong orientation to begin with, and is not rotated again when touched. This is not a good solution anyhow because the PNG is far too large and slow to handle.
Is this a bug or am I doing something wrong?
I was not able to find a proper solution to this and I needed a quick workaround. Below is a function which, given a UIImage, returns a new image which is darkened with a dark alpha fill. The context fill commands could be replaced with other draw or fill routines to provide different types of darkening.
This is un-optimized and was made with minimal knowledge of the graphics api.
You can use this function to set the UIControlStateHighlighted state image so that at least it will be darker.
+ (UIImage *)darkenedImageWithImage:(UIImage *)sourceImage
{
UIImage * darkenedImage = nil;
if (sourceImage)
{
// drawing prep
CGImageRef source = sourceImage.CGImage;
CGRect drawRect = CGRectMake(0.f,
0.f,
sourceImage.size.width,
sourceImage.size.height);
CGContextRef context = CGBitmapContextCreate(NULL,
drawRect.size.width,
drawRect.size.height,
CGImageGetBitsPerComponent(source),
CGImageGetBytesPerRow(source),
CGImageGetColorSpace(source),
CGImageGetBitmapInfo(source)
);
// draw given image and then darken fill it
CGContextDrawImage(context, drawRect, source);
CGContextSetBlendMode(context, kCGBlendModeOverlay);
CGContextSetRGBFillColor(context, 0.f, 0.f, 0.f, 0.5f);
CGContextFillRect(context, drawRect);
// get context result
CGImageRef darkened = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// convert to UIImage and preserve original orientation
darkenedImage = [UIImage imageWithCGImage:darkened
scale:1.f
orientation:sourceImage.imageOrientation];
CGImageRelease(darkened);
}
return darkenedImage;
}
To fix this you need additional normalization function like this:
public extension UIImage {
func normalizedImage() -> UIImage! {
if self.imageOrientation == .Up {
return self
}
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
self.drawInRect(CGRectMake(0, 0, self.size.width, self.size.height))
let normalized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalized
}
}
then you can use it like that:
self.photoButton.sd_setImageWithURL(avatarURL,
forState: .Normal,
placeholderImage: UIImage(named: "user_avatar_placeholder")) {
[weak self] (image, error, cacheType, url) in
guard let strongSelf = self else {
return
}
strongSelf.photoButton.setImage(image.normalizedImage(), forState: .Normal
}

Creating a gradient fill for text using [UIColor colorWithPatternImage:]

I want to create a gradient for the fill color of my text. Currently I am doing it by setting the color of a UILabel's text as
UIImage *image = [UIImage imageNamed:#"GradientFillImage.png"];
myLabel.textColor = [UIColor colorWithPatternImage:image];
Where GradientFillImage.png is a simple image file with a linear gradient painted on it.
This works fine until I want to resize the font. Since the image file is of constant dimensions and does not resize when I resize the font, the gradient fill for the font gets messed up.
How do I create a custom size pattern image and apply it as a fill pattern for text?
I've just finished a UIColor class extension that makes this a 1 line + block thing.
https://github.com/bigkm/UIColor-BlockPattern
CGRect rect = CGRectMake(0.0,0.0,10.0,10.0);
[UIColor colorPatternWithSize:rect.size andDrawingBlock:[[^(CGContextRef c) {
UIImage *image = [UIImage imageNamed:#"FontGradientPink.png"];
CGContextDrawImage(context, rect, [image CGImage]);
} copy] autorelease]];
Ok, I figured it out. Basically, we can override drawRectInText and use our own pattern to color the fill. The advantage of doing this is that we can resize the image into our pattern frame.
First we create a CGPattern object and define a callback to draw the pattern. We also pass the size of the label as a parameter in the callback. We then use the pattern that is drawn in the callback and set it as the fill color of the text:
- (void)drawTextInRect:(CGRect)rect
{
//set gradient as a pattern fill
CGRect info[1] = {rect};
static const CGPatternCallbacks callbacks = {0, &drawImagePattern, NULL};
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, -1.0);
CGPatternRef pattern = CGPatternCreate((void *) info, rect, transform, 10.0, rect.size.height, kCGPatternTilingConstantSpacing, true, &callbacks);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGFloat alpha = 1.0;
CGColorRef patternColorRef = CGColorCreateWithPattern(patternSpace, pattern, &alpha);
CGColorSpaceRelease(patternSpace);
CGPatternRelease(pattern);
self.textColor = [UIColor colorWithCGColor:patternColorRef];
self.shadowOffset = CGSizeZero;
[super drawTextInRect:rect];
}
The callback draws the image into the context. The image is resized as per the frame size that is passed into the callback.
void drawImagePattern(void *info, CGContextRef context)
{
UIImage *image = [UIImage imageNamed:#"FontGradientPink.png"];
CGImageRef imageRef = [image CGImage];
CGRect *rect = info;
CGContextDrawImage(context, rect[0], imageRef);
}