Skip to content

Instantly share code, notes, and snippets.

@trungtran
Created April 22, 2015 01:40
Show Gist options
  • Save trungtran/c015b4a7a9ea8d41134e to your computer and use it in GitHub Desktop.
Save trungtran/c015b4a7a9ea8d41134e to your computer and use it in GitHub Desktop.
//
// UIImage+Dithering.m
//
#import "UIImage+Dithering.h"
typedef unsigned char PixelByte;
@implementation UIImage (Dithering)
- (UIImage *)ditheredImage
{
UIImage *source = self;
CGImageRef imageRef = [source CGImage];
NSInteger width = CGImageGetWidth(imageRef);
NSInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
PixelByte *rawData = malloc(height * width * 4);
NSUInteger pixelBytesPerPixelByte = 4;
NSUInteger pixelBytesPerRow = pixelBytesPerPixelByte * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, pixelBytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Dithering Weights
CGFloat w1 = 7.0 / 16.0;
CGFloat w2 = 3.0 / 16.0;
CGFloat w3 = 5.0 / 16.0;
CGFloat w4 = 1.0 / 16.0;
for (NSInteger y = 0; y < height; y++)
{
for (NSInteger x = 0; x < width; x++)
{
// Set color with 50% threshold
CGFloat luminosity = [self getPixelByte:rawData x:x y:y];
NSInteger oldPixelByte = luminosity;
NSInteger newPixelByte = (oldPixelByte < 128 ? 0 : 255);
[self setPixelByte:rawData x:x y:y PixelByte:newPixelByte];
// Propagate errors to pixel bytes with error and weight
NSInteger quant_error = oldPixelByte - newPixelByte;
PixelByte PixelByte1 = [self getPixelByte:rawData x:x+1 y:y];
PixelByte PixelByte2 = [self getPixelByte:rawData x:x-1 y:y+1];
PixelByte PixelByte3 = [self getPixelByte:rawData x:x y:y+1];
PixelByte PixelByte4 = [self getPixelByte:rawData x:x+1 y:y+1];
[self setPixelByte:rawData x:x+1 y:y
PixelByte:PixelByte1 + w1 * quant_error];
[self setPixelByte:rawData x:x-1 y:y+1
PixelByte:PixelByte2 + w2 * quant_error];
[self setPixelByte:rawData x:x y:y+1
PixelByte:PixelByte3 + w3 * quant_error];
[self setPixelByte:rawData x:x+1 y:y+1
PixelByte:PixelByte4 + w4 * quant_error];
}
}
context = CGBitmapContextCreate(rawData,
CGImageGetWidth( imageRef ),
CGImageGetHeight( imageRef ),
8,
pixelBytesPerRow,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
imageRef = CGBitmapContextCreateImage (context);
UIImage *rawImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGContextRelease(context);
free(rawData);
return rawImage;
}
#pragma mark - RawData accessor/setter utilities
- (void)setPixelByte:(PixelByte *)rawData x:(NSInteger)x y:(NSInteger)y PixelByte:(NSInteger)color
{
NSInteger pixelByteIndex = (y * self.size.width + x) * 4;
// Clamp color
color = MIN(255, color);
color = MAX(0, color);
rawData[pixelByteIndex] = color;
rawData[pixelByteIndex+1] = color;
rawData[pixelByteIndex+2] = color;
}
- (PixelByte)getPixelByte:(PixelByte *)rawData x:(NSInteger)x y:(NSInteger)y
{
NSInteger pixelByteIndex = (y * self.size.width + x) * 4;
CGFloat red = (rawData[pixelByteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[pixelByteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[pixelByteIndex + 2] * 1.0) / 255.0;
CGFloat luminosity = 0.21*red + 0.72*green + 0.07*blue;
return luminosity * 255;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment