Gray scaling the entire view - ios

I am working on a swift based application which shows some events. Each event has time limit after which event expires.
I want to make the event screen grayscale after event expiration for which I have tried things like:
Mask view
if let maskImage = UIImage(named: "MaskImage"){
myView.maskView = UIImageView(image: maskImage)
}
above solution not worked from me as my event screen contains colored images as well
Recursively fetched all subviews and tried to set their backgroundColor but not worked
Changing alpha value of all subViews which shows more faded white color
Query: My event screen has many colorful images and some colorful labels, how can I make all these in grayscale?
Any help would be appreciated.

After some research I've achieved gray scaling effect over entire view something like this:
/**
To convert an image to grayscale
- parameter image: The UIImage to be converted
- returns: The UIImage after conversion
*/
static func convertToGrayScale(_ image: UIImage?) -> UIImage? {
if image == nil {
return nil
}
let imageRect:CGRect = CGRect(x: 0, y: 0, width: image!.size.width, height: image!.size.height)
let colorSpace = CGColorSpaceCreateDeviceGray()
let width = image!.size.width
let height = image!.size.height
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
context?.draw(image!.cgImage!, in: imageRect)
let imageRef = context?.makeImage()
let newImage = UIImage(cgImage: imageRef!)
return newImage
}
Here above method will work for all coloured images in presented view and the same can be used for myView.maskView = UIImageView(image: maskImage).
This will grayscale your entire view and while gray scaling only labels and texts I used this:
// Convert to grayscale
func convertToGrayScaleColor() -> UIColor? {
if self == UIColor.clear {
return UIColor.clear
}
var fRed : CGFloat = 0
var fGreen : CGFloat = 0
var fBlue : CGFloat = 0
var fAlpha: CGFloat = 0
if self.getRed(&fRed, green: &fGreen, blue: &fBlue, alpha: &fAlpha) {
if fRed == 0 && fGreen == 0 && fBlue == 0{
return UIColor.gray
}else{
let grayScaleColor = (fRed + fGreen + fBlue)/3
return UIColor(red: grayScaleColor, green: grayScaleColor, blue: grayScaleColor, alpha: fAlpha)
}
} else {
print("Could not extract RGBA components, so rolling back to default UIColor.grayColor()")
return UIColor.gray
}
}
Above method is a part extension UIColor. Above code is written in Swift 3.0 using xcode 8.1
Please feel free to drop a comment if concerned or confused about anything in the answer.

Related

swift - speed improvement in UIView pixel per pixel drawing

is there a way to improve the speed / performance of drawing pixel per pixel into a UIView?
The current implementation of a 500x500 pixel UIView, is terribly slow.
class CustomView: UIView {
public var context = UIGraphicsGetCurrentContext()
public var redvalues = [[CGFloat]](repeating: [CGFloat](repeating: 1.0, count: 500), count: 500)
public var start = 0
{
didSet{
self.setNeedsDisplay()
}
}
override func draw(_ rect: CGRect
{
super.draw(rect)
context = UIGraphicsGetCurrentContext()
for yindex in 0...499{
for xindex in 0...499 {
context?.setStrokeColor(UIColor(red: redvalues[xindex][yindex], green: 0.0, blue: 0.0, alpha: 1.0).cgColor)
context?.setLineWidth(2)
context?.beginPath()
context?.move(to: CGPoint(x: CGFloat(xindex), y: CGFloat(yindex)))
context?.addLine(to: CGPoint(x: CGFloat(xindex)+1.0, y: CGFloat(yindex)))
context?.strokePath()
}
}
}
}
Thank you very much
When drawing individual pixels, you can use a bitmap context. A bitmap context takes raw pixel data as an input.
The context copies your raw pixel data so you don't have to use paths, which are likely much slower. You can then get a CGImage by using context.makeImage().
The image can then be used in an image view, which would eliminate the need to redraw the whole thing every frame.
If you don't want to manually create a bitmap context, you can use
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
// draw everything into the context
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
Then you can use a UIImageView to display the rendered image.
It is also possible to draw into a CALayer, which does not need to be redrawn every frame but only when resized.
That's how it looks now, are there any optimizations possible or not?
public struct rgba {
var r:UInt8
var g:UInt8
var b:UInt8
var a:UInt8
}
public let imageview = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
let width_input = 500
let height_input = 500
let redPixel = rgba(r:255, g:0, b:0, a:255)
let greenPixel = rgba(r:0, g:255, b:0, a:255)
let bluePixel = rgba(r:0, g:0, b:255, a:255
var pixelData = [rgba](repeating: redPixel, count: Int(width_input*height_input))
pixelData[1] = greenPixel
pixelData[3] = bluePixel
self.view.addSubview(imageview)
imageview.frame = CGRect(x: 100,y: 100,width: 600,height: 600)
imageview.image = draw(pixel: pixelData,width: width_input,height: height_input)
}
func draw(pixel:[rgba],width:Int,height:Int) -> UIImage
{
let colorSpace = CGColorSpaceCreateDeviceRGB()
let data = UnsafeMutableRawPointer(mutating: pixel)
let bitmapContext = CGContext(data: data,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 4*width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
let image = bitmapContext?.makeImage()
return UIImage(cgImage: image!)
}
I took the answer from Manuel and got it working in Swift 5. The main sticking point here was to clear the dangling pointer warning now in Xcode 12.
var image:CGImage?
pixelData.withUnsafeMutableBytes( { (rawBufferPtr: UnsafeMutableRawBufferPointer) in
if let rawPtr = rawBufferPtr.baseAddress {
let bitmapContext = CGContext(data: rawPtr,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 4*width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
image = bitmapContext?.makeImage()
}
})
I did have to move away from the rgba struct approach for front loading the data and moved to direct UInt32 values derived from rawValues in the enum. The 'append' or 'replaceInRange' approach to updating an existing array took hours (my bitmap was LARGE) and ended up exhausting swap space on my computer.
enum Color: UInt32 { // All 4 bytes long with full opacity
case red = 4278190335 // 0xFF0000FF
case yellow = 4294902015
case orange = 4291559679
case pink = 4290825215
case violet = 4001558271
case purple = 2147516671
case green = 16711935
case blue = 65535 // 0x0000FFFF
}
With this approach I was able to quickly build a Data buffer with that data amount via:
func prepareColorBlock(c:Color) -> Data {
var rawData = withUnsafeBytes(of:c.rawValue) { Data($0) }
rawData.reverse() // Byte order is reveresed when defined
var dataBlock = Data()
dataBlock.reserveCapacity(100)
for _ in stride(from: 0, to: 100, by: 1) {
dataBlock.append(rawData)
}
return dataBlock
}
With that I just appended each of these blocks into my mutable Data instance 'pixelData' and we are off. You can tweak how the data is assembled, as I just wanted to generate some color bars in a UIImageView to validate the work. For a 800x600 view, it took about 2.3 seconds to generate and render the whole thing.
Again, hats off to Manuel for pointing me in the right direction.

View with Custom design

Need to design single view like the attached design in swift and this to be visible at all my collecitonview cell, how to achieve this anyone have idea about this
I have tried it in a test project. This is my way:
Open Photoshop or a similar tool and make a picture with a translucent background.
Use the PS tools to draw a figure the way you want it.
Save it as a PNG. Open Xcode. Put a UIImageView into your UIViewController. Put the PDF into your Assets folder and set this Image as the Image for the UIImageView. Set the contraints.
Set the following code into the UIViewController.swift file:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var testImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(doubleTapped))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
}
func doubleTapped() {
let image = UIImage(named: "test")
testImageView.image = image?.maskWithColor(color: UIColor.blue)
}
}
extension UIImage {
func maskWithColor(color: UIColor) -> UIImage? {
let maskImage = self.cgImage
let width = self.size.width
let height = self.size.height
let bounds = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: width, height: height))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let bitmapContext = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) //needs rawValue of bitmapInfo
bitmapContext!.clip(to: bounds, mask: maskImage!)
bitmapContext!.setFillColor(color.cgColor)
bitmapContext!.fill(bounds)
//is it nil?
if let cImage = bitmapContext!.makeImage() {
let coloredImage = UIImage(cgImage: cImage)
return coloredImage
} else {
return nil
}
}
}
Start the app and touch the display. The color changes from black to blue once tapped. Now you should have all the tools to do whatever you want too... The Code is in Swift 3 language.
You can set this UIImageView into your UICollectionViewCell and set the UIColor with the function provided.
And here is a function to set a random UIColor.

Image masking fails on iOS10 beta3

For some time now we use the following code to mask a grayscale image without transparency to a coloured image.
This always worked fine until Apple released iOS 10 beta 3. Suddenly the mask is not applied anymore resulting in just a square box being returned with te given color.
The logic behind this can be found at
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_images/dq_images.html
under the header Masking an Image with an Image Mask
The logic of this code:
* Take an grayscale image without alpha
* Create an solid image with the given color
* Create a mask from the given image
* Mask the solid image with the created mask
* Output is a masked image with also respect for colors in between (gray might be light red i.e.).
Has anyone an idea how to fix this function?
If you have XCode 8 beta 3 you can run this code and on a simulator lower than iOS 10 this will work correct and on iOS 10 it will just create a square box
Example image:
public static func image(maskedWith color: UIColor, imageNamed imageName: String) -> UIImage? {
guard let image = UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal) else {
return nil
}
guard image.size != CGSize.zero else {
return nil
}
guard
let maskRef = image.cgImage,
let colorImage = self.image(with: color, size: image.size),
let cgColorImage = colorImage.cgImage,
let dataProvider = maskRef.dataProvider
else {
return nil
}
guard
let mask = CGImage(maskWidth: maskRef.width, height: maskRef.height, bitsPerComponent: maskRef.bitsPerComponent, bitsPerPixel: maskRef.bitsPerPixel, bytesPerRow: maskRef.bytesPerRow, provider: dataProvider, decode: nil, shouldInterpolate: true),
let masked = cgColorImage.masking(mask)
else {
return nil
}
let result = UIImage(cgImage: masked, scale: UIScreen.main().scale, orientation: image.imageOrientation)
return result
}
public static func image(with color: UIColor, size: CGSize) -> UIImage? {
guard size != CGSize.zero else {
return nil
}
let rect = CGRect(origin: CGPoint.zero, size: size)
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main().scale)
defer {
UIGraphicsEndImageContext()
}
guard let context = UIGraphicsGetCurrentContext() else {
return nil
}
context.setFillColor(color.cgColor)
context.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image;
}
This issue has been solved in iOS10 beta 4.

Convert a Double Array to UIImage in swift

Is it possible to convert a double array to UIImage using UIImage function?
var ImageDouble = [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]
UIImage function asks for String. I tried converting the array on individual element to strings and the whole thing to string, I get a nill result. Can someone help?
[Edit:]
As it seems I was not really clear on my problem. To make it more clear, I am developing a software which crops numbers & operators from a mathematical expression and shows me all the chunks one by one. Now, how do I go about it in Swift? :
Step-1: I have a grayscale image provided. I read it using UIImage function. I convert it to a custom RGBA function as follows so that I can deal on a pixel level like we do in MATLAB or Python or C++. The function is inspired from many websites's suggestions to use it.
struct Pixel {
var value: UInt32
var red: UInt8 {
get { return UInt8(value & 0xFF) }
set { value = UInt32(newValue) | (value & 0xFFFFFF00) }
}
var green: UInt8 {
get { return UInt8((value >> 8) & 0xFF) }
set { value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF) }
}
var blue: UInt8 {
get { return UInt8((value >> 16) & 0xFF) }
set { value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF) }
}
var alpha: UInt8 {
get { return UInt8((value >> 24) & 0xFF) }
set { value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF) }
}
}
struct RGBA {
var pixels: UnsafeMutableBufferPointer<Pixel>
var width: Int
var height: Int
init?(image: UIImage) {
guard let cgImage = image.CGImage else { return nil }
width = Int(image.size.width)
height = Int(image.size.height)
let bitsPerComponent = 8
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let imageData = UnsafeMutablePointer<Pixel>.alloc(width * height)
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
guard let imageContext = CGBitmapContextCreate(imageData, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo) else { return nil }
CGContextDrawImage(imageContext, CGRect(origin: CGPointZero, size: image.size), cgImage)
pixels = UnsafeMutableBufferPointer<Pixel>(start: imageData, count: width * height)
}
func toUIImage() -> UIImage? {
let bitsPerComponent = 8
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
let imageContext = CGBitmapContextCreateWithData(pixels.baseAddress, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo, nil, nil)
guard let cgImage = CGBitmapContextCreateImage(imageContext) else {return nil}
let image = UIImage(CGImage: cgImage)
return image
}
}
Step-2: Now I take one channel of the image [Red: it does not matter which one I take, since all the channels have same output] and apply my custom median filter of kernel size 5x5.
Step-3: Next, I do the binarization of the image using Nibblack approximation,where I used averaging filter and standard deviation.
Step-4: After that, I used connected component labelling to separate out different connected components of the image.
Step-5: Finally, I need to crop the labelled images and resize it. For cropping from the original image , I know the location by using a smart location algorithm. For resizing, I want to use Core Graphics Resizing filter. However, for that I need to convert my current output [a two-dimensional array or flattened] to UIImage or CGImage.
That's my real question: How do I convert to UIImage or CGImage from two-dimensional array or flattened array which are of type Array<Double> or Array<Int>?
Try this:
var ImageDouble = [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]
let images = ImageDouble.map{ UIImage(named: "\($0)")}
the map function will transform your array to another array.
Just try this:
var str:[String] = [];
for (var i=0;i<ImageDouble.count;i++){
str.append(String(ImageDouble[i]));
}
print(str)
But why would you pass a String of numbers to an UIImage? Are images in your asset named as numbers?
Looks like you want to render 1, 2, etc into a bitmap.
First you need a function to do that, and then use flatMap on the array of doubles using this. This will ensure you get an array of UIImages in a safe way by filtering if any nil results when we try to create images.
func createNumberImage(number:Double) -> UIImage?{
//Set a frame that you want.
let frame = CGRect(x: 0, y: 0, width: 200, height: 200)
let view = UIView(frame:frame )
view.backgroundColor = UIColor.redColor()
let label = UILabel(frame:frame)
view.addSubview(label)
label.font = UIFont.systemFontOfSize(100);
label.text = String(format: "%.1lf", arguments:[number])
label.textAlignment = NSTextAlignment.Center
view
UIGraphicsBeginImageContext(view.bounds.size);
view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot
}
let doubles = [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0]
//Get the array of UIImage objects.
let images = doubles.flatMap { createNumberImage($0) }
You can use playground to verify this working. Also you might want to set proper background and text colors and font size to suite your requirements.

How to apply a tintColor to a UIImage?

I have a UIImage that is a small symbol that is all black. The UIImage is getting set in a custom UIButton subclass I have. Is it possible to have the image to apply the tintColor to it, so instead of the black image it changes colors to whatever the tintColor is?
I'm just trying to avoid creating new assets.
// here I want defaultImageName (that is black) to use the tintColor (that is white)
[self setImage:[UIImage imageNamed:defaultImageName] forState:UIControlStateNormal];
If you are just supporting iOS 7 you can use tintColor and UIImageRenderingModeAlwaysTemplate
This article covers that:
https://www.captechconsulting.com/blogs/ios-7-tutorial-series-tint-color-and-easy-app-theming
If you need to support an earlier version you may want to consider this thread
How would I tint an image programmatically on the iPhone?
Swift 4, copy-paste solution
#IBOutlet weak var iconImageView: UIImageView!
iconImageView.image = UIImage(imageLiteralResourceName: "myImageName").withRenderingMode(.alwaysTemplate)
iconImageView.tintColor = UIColor.red
On iOS 13+ you can use the following:
UIImage(named: "img_name")?.withTintColor(.red)
https://developer.apple.com/documentation/uikit/uiimage/3327300-withtintcolor
Try this:
func tinted(with color: UIColor) -> UIImage? {
defer { UIGraphicsEndImageContext() }
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color.set()
self.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: self.size))
return UIGraphicsGetImageFromCurrentImageContext()
}
For example:
button.setImage(UIImage(systemName: "checkmark.circle")?.tinted(with: .systemGray), for: .normal)
Here's how I use tint colors and opacities in IOS 9 with Swift -
//apply a color to an image
//ref - http://stackoverflow.com/questions/28427935/how-can-i-change-image-tintcolor
//ref - https://www.captechconsulting.com/blogs/ios-7-tutorial-series-tint-color-and-easy-app-theming
func getTintedImage() -> UIImageView {
var image :UIImage
var imageView :UIImageView
image = UIImage(named: "someAsset")!
let size : CGSize = image.size
let frame : CGRect = CGRectMake((UIScreen.mainScreen().bounds.width-86)/2, 600, size.width, size.height)
let redCover : UIView = UIView(frame: frame)
redCover.backgroundColor = UIColor.redColor()
redCover.layer.opacity = 0.75
imageView = UIImageView();
imageView.image = image.imageWithRenderingMode(UIImageRenderingMode.Automatic)
imageView.addSubview(redCover)
return imageView
}
Why not use image filtering
btn.setImage(image, for: UIControl.State.normal)
btn.setImage(image.disabled, for: UIControl.State.disabled)
Use CoreImage to do image filter
extension UIImage
{
/// Create a grayscale image with alpha channel. Is 5 times faster than grayscaleImage().
/// - Returns: The grayscale image of self if available.
var disabled: UIImage?
{
// Create image rectangle with current image width/height * scale
let pixelSize = CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale)
let imageRect = CGRect(origin: CGPoint.zero, size: pixelSize)
// Grayscale color space
let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceGray()
// Create bitmap content with current image size and grayscale colorspace
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
if let context: CGContext = CGContext(data: nil, width: Int(pixelSize.width), height: Int(pixelSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)
{
// Draw image into current context, with specified rectangle
// using previously defined context (with grayscale colorspace)
guard let cg = self.cgImage else{
return nil
}
context.draw(cg, in: imageRect)
// Create bitmap image info from pixel data in current context
if let imageRef: CGImage = context.makeImage(){
let bitmapInfoAlphaOnly = CGBitmapInfo(rawValue: CGImageAlphaInfo.alphaOnly.rawValue)
guard let context = CGContext(data: nil, width: Int(pixelSize.width), height: Int(pixelSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfoAlphaOnly.rawValue) else{
return nil
}
context.draw(cg, in: imageRect)
if let mask: CGImage = context.makeImage() {
// Create a new UIImage object
if let newCGImage = imageRef.masking(mask){
// Return the new grayscale image
return UIImage(cgImage: newCGImage, scale: self.scale, orientation: self.imageOrientation)
}
}
}
}
// A required variable was unexpected nil
return nil
}
}
Of course, in Swift 5
Swift5 Extension
extension UIImage {
var template: UIImage? {
return self.withRenderingMode(.alwaysTemplate)
}
}
Usage:
UIImageView
let imgView = UIImageView()
imgView.tintColor = UIColor.red
imgView.image = UIImage(named: "IMAGE_NAME_HERE")?.template
UIButton
let button = UIButton(type: .custom)
button.tintColor = UIColor.red
button.setImage(UIImage(named: "IMAGE_NAME_HERE")?.template, for: .normal)
Use this simple extension to UIImageView
#IBInspectable var tintedColor: UIColor{
get{
return tintColor
}
set{
image = image?.withRenderingMode(.alwaysTemplate)
tintColor = newValue
}
}

Resources