Replace Exactly One Pixel in an Image and Put It in Another Image via Swift

Replace exactly one pixel in an image and put it in another image via Swift

If you want to set just one pixel, you can create a small color kernel that compares a passed target coordinate with the current coordinate and colors the output appropriately. For example:

let kernel = CIColorKernel(string:
"kernel vec4 setPixelColorAtCoord(__sample pixel, vec2 targetCoord, vec4 targetColor)" +
"{" +
" return int(targetCoord.x) == int(destCoord().x) && int(targetCoord.y) == int(destCoord().y) ? targetColor : pixel; " +
"}"
)

Then, given an image, let's say a solid red:

let image = CIImage(color: CIColor(red: 1, green: 0, blue: 0))
.imageByCroppingToRect(CGRect(x: 0, y: 0, width: 640, height: 640))

If we want to set the pixel at 500, 100 to blue:

let targetCoord = CIVector(x: 500, y: 100)
let targetColor = CIColor(red: 0, green: 0, blue: 1)

The following will create a CIImage named final with a single blue pixel in a sea of red:

let args = [image, targetCoord, targetColor]
let final = kernel?.applyWithExtent(image.extent, arguments: args)

If you want to draw more than one pixel, this may not be the best solution though.

Simon

Replace a particular color inside an image with another color

  1. You have to iterate through each pixel in the image and take its rgb
    values
  2. Check its rgb values matches your fromColor rgb values, if yes
    change the pixel value to your toColor rgb values.If not, just leave
    that pixel and go to next one..

Wrote a function from memory..errors possible..correct yourself

-(UIImage*)changeColor:(UIImage*)myImage fromColor:(UIColor*)fromColor toColor:(UIColor*)toColor{
CGImageRef originalImage = [myImage CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext =
CGBitmapContextCreate(NULL,CGImageGetWidth(originalImage),CGImageGetHeight(originalImage),
8,CGImageGetWidth(originalImage)*4,colorSpace,kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(bitmapContext, CGRectMake(0, 0,
CGBitmapContextGetWidth(bitmapContext),CGBitmapContextGetHeight(bitmapContext)),
originalImage);
UInt8 *data = CGBitmapContextGetData(bitmapContext);
int numComponents = 4;
int bytesInContext = CGBitmapContextGetHeight(bitmapContext) *
CGBitmapContextGetBytesPerRow(bitmapContext);
double redIn, greenIn, blueIn,alphaIn;
CGFloat fromRed,fromGreen,fromBlue,fromAlpha;
CGFloat toRed,toGreen,toBlue,toAlpha;

//Get RGB values of fromColor
int fromCountComponents = CGColorGetNumberOfComponents([fromColor CGColor]);
if (fromCountComponents == 4) {
const CGFloat *_components = CGColorGetComponents([fromColor CGColor]);
fromRed = _components[0];
fromGreen = _components[1];
fromBlue = _components[2];
fromAlpha = _components[3];
}

//Get RGB values for toColor
int toCountComponents = CGColorGetNumberOfComponents([toColor CGColor]);
if (toCountComponents == 4) {
const CGFloat *_components = CGColorGetComponents([toColor CGColor]);
toRed = _components[0];
toGreen = _components[1];
toBlue = _components[2];
toAlpha = _components[3];
}

//Now iterate through each pixel in the image..
for (int i = 0; i < bytesInContext; i += numComponents) {
//rgba value of current pixel..
redIn = (double)data[i]/255.0;
greenIn = (double)data[i+1]/255.0;
blueIn = (double)data[i+2]/255.0;
alphaIn = (double)data[i+3]/255.0;

//now you got current pixel rgb values...check it curresponds with your fromColor
if( redIn == fromRed && greenIn == fromGreen && blueIn == fromBlue ){
//image color matches fromColor, then change current pixel color to toColor
data[i] = toRed;
data[i+1] = toGreen;
data[i+2] = toBlue;
data[i+3] = toAlpha;
}
}
CGImageRef outImage = CGBitmapContextCreateImage(bitmapContext);
myImage = [UIImage imageWithCGImage:outImage];
CGImageRelease(outImage);
return myImage;
}

I hope you are not calling this function every time...It is a bit processor heavy..And if I was your processor, I wouldn't be happy with you..:)

Changing specific pixels in a UIImage based on RBG values to a different RBG color in swift

I tweaked similar code to change all UIImage white pixels into clear pixels

(Swift 3)

func processPixelsInImage(_ image: UIImage) -> UIImage? {
guard let inputCGImage = image.cgImage else {
//print("unable to get cgImage")
return nil
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo

guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
//print("unable to create context")
return nil
}
context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))

guard let buffer = context.data else {
//print("unable to get context data")
return nil
}

let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)

let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255)
let clear = RGBA32(red: 0, green: 0, blue: 0, alpha: 0)

for row in 0 ..< Int(height) {
for column in 0 ..< Int(width) {
let offset = row * width + column
if pixelBuffer[offset] == white {
pixelBuffer[offset] = clear
}
}
}

let outputCGImage = context.makeImage()!
let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation)

return outputImage

}

struct RGBA32: Equatable {
var color: UInt32

var red: UInt8 {
return UInt8((color >> 24) & 255)
}

var green: UInt8 {
return UInt8((color >> 16) & 255)
}

var blue: UInt8 {
return UInt8((color >> 8) & 255)
}

var alpha: UInt8 {
return UInt8((color >> 0) & 255)
}

init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
color = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
}

static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}

}

How do I change the pixel values of UIImage with daltonization algorithm in Swift

You defined

pixelBuffer = buffer.bindMemory(to: RGBA32.self...

But then later, throughout the rest of your code, you seem to think that every offset within pixelBuffer is a UInt8. It isn't. It's an RGBA32!

For example, you say:

let r = pixelBuffer[offset]
let g = pixelBuffer[offset + 1]
let b = pixelBuffer[offset + 2]

That seems to imply you think r, g, and b are numbers (e.g. UInt8 values). They are not. You bound pixelBuffer to RGBA32, so every offset within pixelBuffer is an RGBA32. That is what memory binding means.

All your errors, it seems to me, stem from that one wrong idea.

If your goal is to pick up the red, green, blue, and alpha components of the nth pixel in the pixelBuffer, those would be respectively pixelBuffer[n].redComponent, pixelBuffer[n].greenComponent, pixelBuffer[n].blueComponent, and pixelBuffer[n].alphaComponent.

Even then you won't be able to multiply a component directly by a UInt32, because you cannot multiply a UInt32 by a UInt8; but you will be a lot closer to accomplishing something useful.

Change color of certain pixels in a UIImage

You have to extract the pixel buffer of the image, at which point you can loop through, changing pixels as you see fit. At the end, create a new image from the buffer.

In Swift 3, this looks like:

func processPixels(in image: UIImage) -> UIImage? {
guard let inputCGImage = image.cgImage else {
print("unable to get cgImage")
return nil
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let width = inputCGImage.width
let height = inputCGImage.height
let bytesPerPixel = 4
let bitsPerComponent = 8
let bytesPerRow = bytesPerPixel * width
let bitmapInfo = RGBA32.bitmapInfo

guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
print("unable to create context")
return nil
}
context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))

guard let buffer = context.data else {
print("unable to get context data")
return nil
}

let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)

for row in 0 ..< Int(height) {
for column in 0 ..< Int(width) {
let offset = row * width + column
if pixelBuffer[offset] == .black {
pixelBuffer[offset] = .red
}
}
}

let outputCGImage = context.makeImage()!
let outputImage = UIImage(cgImage: outputCGImage, scale: image.scale, orientation: image.imageOrientation)

return outputImage
}

struct RGBA32: Equatable {
private var color: UInt32

var redComponent: UInt8 {
return UInt8((color >> 24) & 255)
}

var greenComponent: UInt8 {
return UInt8((color >> 16) & 255)
}

var blueComponent: UInt8 {
return UInt8((color >> 8) & 255)
}

var alphaComponent: UInt8 {
return UInt8((color >> 0) & 255)
}

init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
let red = UInt32(red)
let green = UInt32(green)
let blue = UInt32(blue)
let alpha = UInt32(alpha)
color = (red << 24) | (green << 16) | (blue << 8) | (alpha << 0)
}

static let red = RGBA32(red: 255, green: 0, blue: 0, alpha: 255)
static let green = RGBA32(red: 0, green: 255, blue: 0, alpha: 255)
static let blue = RGBA32(red: 0, green: 0, blue: 255, alpha: 255)
static let white = RGBA32(red: 255, green: 255, blue: 255, alpha: 255)
static let black = RGBA32(red: 0, green: 0, blue: 0, alpha: 255)
static let magenta = RGBA32(red: 255, green: 0, blue: 255, alpha: 255)
static let yellow = RGBA32(red: 255, green: 255, blue: 0, alpha: 255)
static let cyan = RGBA32(red: 0, green: 255, blue: 255, alpha: 255)

static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

static func ==(lhs: RGBA32, rhs: RGBA32) -> Bool {
return lhs.color == rhs.color
}
}

For Swift 2 rendition, see previous revision of this answer.

How to replace a given color with another on an opaque UIImage

I couldn't see any answers on the 'duplicates' (this question shouldn't have been flagged as a duplicate) that will let you replace a given color with another color and work on an opaque image, so I decided to add one that would.


I created a UIImage category to do this, it basically works by looping through each pixel and detecting how close it is to a given colour, and blends it with your replacement colour if it is.

This will work for images with both transparency and opaque backgrounds.

@implementation UIImage (UIImageColorReplacement)

-(UIImage*) imageByReplacingColor:(UIColor*)sourceColor withMinTolerance:(CGFloat)minTolerance withMaxTolerance:(CGFloat)maxTolerance withColor:(UIColor*)destinationColor {

// components of the source color
const CGFloat* sourceComponents = CGColorGetComponents(sourceColor.CGColor);
UInt8* source255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) source255Components[i] = (UInt8)round(sourceComponents[i]*255.0);

// components of the destination color
const CGFloat* destinationComponents = CGColorGetComponents(destinationColor.CGColor);
UInt8* destination255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) destination255Components[i] = (UInt8)round(destinationComponents[i]*255.0);

// raw image reference
CGImageRef rawImage = self.CGImage;

// image attributes
size_t width = CGImageGetWidth(rawImage);
size_t height = CGImageGetHeight(rawImage);
CGRect rect = {CGPointZero, {width, height}};

// bitmap format
size_t bitsPerComponent = 8;
size_t bytesPerRow = width*4;
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;

// data pointer
UInt8* data = calloc(bytesPerRow, height);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

// create bitmap context
CGContextRef ctx = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGContextDrawImage(ctx, rect, rawImage);

// loop through each pixel's components
for (int byte = 0; byte < bytesPerRow*height; byte += 4) {

UInt8 r = data[byte];
UInt8 g = data[byte+1];
UInt8 b = data[byte+2];

// delta components
UInt8 dr = abs(r-source255Components[0]);
UInt8 dg = abs(g-source255Components[1]);
UInt8 db = abs(b-source255Components[2]);

// ratio of 'how far away' each component is from the source color
CGFloat ratio = (dr+dg+db)/(255.0*3.0);
if (ratio > maxTolerance) ratio = 1; // if ratio is too far away, set it to max.
if (ratio < minTolerance) ratio = 0; // if ratio isn't far enough away, set it to min.

// blend color components
data[byte] = (UInt8)round(ratio*r)+(UInt8)round((1.0-ratio)*destination255Components[0]);
data[byte+1] = (UInt8)round(ratio*g)+(UInt8)round((1.0-ratio)*destination255Components[1]);
data[byte+2] = (UInt8)round(ratio*b)+(UInt8)round((1.0-ratio)*destination255Components[2]);

}

// get image from context
CGImageRef img = CGBitmapContextCreateImage(ctx);

// clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(data);
free(source255Components);
free(destination255Components);

UIImage* returnImage = [UIImage imageWithCGImage:img];
CGImageRelease(img);

return returnImage;
}

@end

Usage:

UIImage* colaImage = [UIImage imageNamed:@"cola1.png"];
UIImage* blackColaImage = [colaImage imageByReplacingColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] withMinTolerance:0.5 withMaxTolerance:0.6 withColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1]];

The minTolerance is the point at which pixels will start to blend with the replacement colour (rather than being replaced). The maxTolerance is the point at which the pixels will stop being blended.

Before:

Sample Image

After:

Sample Image

The result is a little aliased, but bear in mind that your original image was fairly small. This will work much better with a higher resolution image. You can also play about with the tolerances to get even better results!



Related Topics



Leave a reply



Submit