CGBitmapContextCreate and interlaced? image - iphone

I'm converting some image drawing code from Cairo to Quartz and I'm slowly making progress and learning Quartz along the way but I've run into a problem with the image format.
In the Cairo version it works like this:
unsigned short *d = (unsigned short*)imageSurface->get_data();
int stride = imageSurface->get_stride() >> 1;
int height = imageHeight;
int width = imageWidth;
do {
d = *p++; // p = raw image data
width --;
if( width == 0 ) {
height --;
width = imageWidth;
d += stride;
}
} while( height );
Now this produces the image as expected on the Cairo::ImageSurface. I've converted this over to how use Quartz and it is making progress but I'm not sure where I'm going wrong:
NSInteger pixelLen = (width * height) * 8;
unsigned char *d = (unsigned char*)malloc(pixelLen);
unsigned char *rawPixels = d;
int height = imageHeight;
int width = imageWidth;
do {
d = *p++; // p = raw image data
width --;
if( width == 0 ) {
height --;
width = imageWidth;
}
} while( height );
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rawPixels, imageWidth, imageHeight, 8, tileSize * sizeof(int), colorSpace, kCGBitmapByteOrderDefault);
CGImageRef image = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
UIImage *resultUIImage = [UIImage imageWithCGImage:image];
CGImageRelease(image);
Now this is obviously heading in the right direction as it produces something that looks a bit like the desired image but it creates 4 copies of the image in a row, each with different pixels filled in so I'm assuming this is an interlaced image (I don't know a great deal about image formats) and that I need to somehow combine them somehow to create a complete image but I don't know how to do that with Quartz.
I think the stride has something to do with the problem but from what I understand this is the byte distance from one row of pixels to another which would not be relevant in the context of Quartz?

It sounds like stride would correspond to rowBytes or bytesPerRow. This value is important because it is not necessarily equal to width * bytesPerPixel because rows might be padded to optimized offsets.
It's not completely obvious what the Cairo code is doing, and it doesn't look quite correct either. Either way, without the stride part, your loop makes no sense because it makes an exact copy of the bytes.
The loop in the Cairo code is copying a row of bytes, then jumping over the next row of data.

Related

Image Circular Wrap in iOS

I have a problem - I want to create a circular wrap function which will wrap an image as depicted below:
This is available in OSX however is not available on iOS.
My logic so far has been:
Split the image up into x sections and for each section:
Rotate alpha degrees
Scale the image in the x axis to create a diamond shaped 'warped' effect of the image
Rotate back 90 - atan((h / 2) / (w / 2))
Translate the offset
My problem is that this seems inaccurate and I have been unable to mathematically figure out how to do this correctly - any help would be massively appreciated.
Link to OSX docs for CICircularWrap:
https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CICircularWrap
Since CICircularWrap is not supported on iOS (EDIT: it is now - check answer below), one has to code his own effect for now. Probably the simplest way is to compute the transformation from polar to cartesian coordinate systems and then interpolate from the source image. I've come up with this simple (and frankly quite slow - it can be much optimised) algorithm:
#import <QuartzCore/QuartzCore.h>
CGContextRef CreateARGBBitmapContext (size_t pixelsWide, size_t pixelsHigh)
{
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (int)(pixelsWide * 4);
bitmapByteCount = (int)(bitmapBytesPerRow * pixelsHigh);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
fprintf(stderr, "Error allocating color space\n");
return NULL;
}
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
fprintf (stderr, "Memory not allocated!");
CGColorSpaceRelease( colorSpace );
return NULL;
}
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
if (context == NULL)
{
free (bitmapData);
fprintf (stderr, "Context not created!");
}
// Make sure and release colorspace before returning
CGColorSpaceRelease( colorSpace );
return context;
}
CGImageRef circularWrap(CGImageRef inImage,CGFloat bottomRadius, CGFloat topRadius, CGFloat startAngle, BOOL clockWise, BOOL interpolate)
{
if(topRadius < 0 || bottomRadius < 0) return NULL;
// Create the bitmap context
int w = (int)CGImageGetWidth(inImage);
int h = (int)CGImageGetHeight(inImage);
//result image side size (always a square image)
int resultSide = 2*MAX(topRadius, bottomRadius);
CGContextRef cgctx1 = CreateARGBBitmapContext(w,h);
CGContextRef cgctx2 = CreateARGBBitmapContext(resultSide,resultSide);
if (cgctx1 == NULL || cgctx2 == NULL)
{
return NULL;
}
// Get image width, height. We'll use the entire image.
CGRect rect = {{0,0},{w,h}};
// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawImage(cgctx1, rect, inImage);
// Now we can get a pointer to the image data associated with the bitmap
// context.
int *data1 = CGBitmapContextGetData (cgctx1);
int *data2 = CGBitmapContextGetData (cgctx2);
int resultImageSize = resultSide*resultSide;
double temp;
for(int *p = data2, pos = 0;pos<resultImageSize;p++,pos++)
{
*p = 0;
int x = pos%resultSide-resultSide/2;
int y = -pos/resultSide+resultSide/2;
CGFloat phi = modf(((atan2(x, y)+startAngle)/2.0/M_PI+0.5),&temp);
if(!clockWise) phi = 1-phi;
phi*=w;
CGFloat r = ((sqrtf(x*x+y*y))-topRadius)*h/(bottomRadius-topRadius);
if(phi>=0 && phi<w && r>=0 && r<h)
{
if(!interpolate || phi >= w-1 || r>=h-1)
{
//pick the closest pixel
*p = data1[(int)r*w+(int)phi];
}
else
{
double dphi = modf(phi, &temp);
double dr = modf(r, &temp);
int8_t* c00 = (int8_t*)(data1+(int)r*w+(int)phi);
int8_t* c01 = (int8_t*)(data1+(int)r*w+(int)phi+1);
int8_t* c10 = (int8_t*)(data1+(int)r*w+w+(int)phi);
int8_t* c11 = (int8_t*)(data1+(int)r*w+w+(int)phi+1);
//interpolate components separately
for(int component = 0; component < 4; component++)
{
double avg = ((*c00 & 0xFF)*(1-dphi)+(*c01 & 0xFF)*dphi)*(1-dr)+((*c10 & 0xFF)*(1-dphi)+(*c11 & 0xFF)*dphi)*dr;
*p += (((int)(avg))<<(component*8));
c00++; c10++; c01++; c11++;
}
}
}
}
CGImageRef result = CGBitmapContextCreateImage(cgctx2);
// When finished, release the context
CGContextRelease(cgctx1);
CGContextRelease(cgctx2);
// Free image data memory for the context
if (data1) free(data1);
if (data2) free(data2);
return result;
}
Use the circularWrap function with parameters:
CGImageRef inImage the source image
CGFloat bottomRadius the bottom side of the source image will transform into a circle with this radius
CGFloat topRadius the same for the top side of the source image, this can be larger or smaler than the bottom radius. (results in wraping around top/bottom of the image)
CGFloat startAngle the angle in which the left and right sides of the source image will transform. BOOL clockWise direction of rendering
BOOL interpolate a simple anti-aliasing algorithm. Only the inside of the image is interpolated
some samples (top left is the source image):
with code:
image1 = [UIImage imageWithCGImage:circularWrap(sourceImage.CGImage,0,300,0,YES,NO)];
image2 = [UIImage imageWithCGImage:circularWrap(sourceImage.CGImage,100,300,M_PI_2,NO,YES)];
image3 = [UIImage imageWithCGImage:circularWrap(sourceImage.CGImage,300,200,M_PI_4,NO,YES)];
image4 = [UIImage imageWithCGImage:circularWrap(sourceImage.CGImage,250,300,0,NO,NO)];
enjoy! :)
Apple have added CICircularWrap to iOS 9
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CICircularWrap
Wraps an image around a transparent circle.
Localized Display Name
Circular Wrap Distortion
Availability
Available in OS X v10.5 and later and in iOS 9 and later.

iPhone App - Display pixel data present in buffer on screen

I have the source code for a video decoder application written in C, which I'm now porting on iphone.
My problem is as follows:
I have RGBA pixel data for a frame in a buffer that I need to display on the screen. My buffer is of type unsigned char. (I cannot change it to any other data type as the source code is too huge and not written by me.)
Most of the links I found on the net say about how to "draw and display pixels" on the screen or how to "display pixels present in an array", but none of then say how to "display pixel data present in a buffer".
I'm planning to use quartz 2D. All I need to do is just display the buffer contents on the screen. No modifications! Although my problem sounds very simple, there isn't any API that I could find to do the same. I couldn't find any appropriate link or document that was useful enough.
Kindly help!
Thanks in advance.
You can use the CGContext data structure to create a CGImage from raw pixel data. I've quickly written a basic example:
- (CGImageRef)drawBufferWidth:(size_t)width height:(size_t)height pixels:(void *)pixels
{
unsigned char (*buf)[width][4] = pixels;
static CGColorSpaceRef csp = NULL;
if (!csp) {
csp = CGColorSpaceCreateDeviceRGB();
}
CGContextRef ctx = CGBitmapContextCreate(
buf,
width,
height,
8, // 8 bits per pixel component
width * 4, // 4 bytes per row
csp,
kCGImageAlphaPremultipliedLast
);
CGImageRef img = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
return img;
}
You can call this method like this (I've used a view controller):
- (void)viewDidLoad
{
[super viewDidLoad];
const size_t width = 320;
const size_t height = 460;
unsigned char (*buf)[width][4] = malloc(sizeof(*buf) * height);
// fill up `buf` here
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
buf[y][x][0] = x * 255 / width;
buf[y][x][1] = y * 255 / height;
buf[y][x][2] = 0;
buf[y][x][3] = 255;
}
}
CGImageRef img = [self drawBufferWidth:320 height:460 pixels:buf];
self.imageView.image = [UIImage imageWithCGImage:img];
CGImageRelease(img);
}

Why am I getting these weird results when editing the image?

I'm trying to do something very simple:
1) Draw a UIImage into a CG bitmap context
2) Get a pointer to the data of the image
3) iterate over all pixels and just set all R G B components to 0 and alpha to 255. The result should appear pure black.
This is the original image I am using. 200 x 40 pixels, PNG-24 ARGB premultiplied alpha (All alpha values == 255):
This is the result (screenshot from Simulator), when I do not modify the pixels. Looks good:
This is the result, when I do the modifications! It looks like if the modification was incomplete. But the for-loops went over EVERY single pixel. The counter proves it: Console reports modifiedPixels = 8000 which is exactly 200 x 40 pixels. It looks always exactly the same.
Note: The PNG image I use has no alpha < 255. So no transparent pixels.
This is how I create the context. Nothing special...
int bitmapBytesPerRow = (width * 4);
int bitmapByteCount = (bitmapBytesPerRow * imageHeight);
colorSpace = CGColorSpaceCreateDeviceRGB();
bitmapData = malloc(bitmapByteCount);
bitmapContext = CGBitmapContextCreate(bitmapData,
width,
height,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
CGImageAlphaPremultipliedFirst);
Next, I draw the image into that bitmapContext, and obtain the data like this:
void *data = CGBitmapContextGetData(bitmapContext);
This is the code which iterates over the pixels to modify them:
size_t bytesPerRow = CGImageGetBytesPerRow(img);
NSInteger modifiedPixels = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
long int offset = bytesPerRow * y + 4 * x;
// ARGB
unsigned char alpha = data[offset];
unsigned char red = data[offset+1];
unsigned char green = data[offset+2];
unsigned char blue = data[offset+3];
data[offset] = 255;
data[offset+1] = 0;
data[offset+2] = 0;
data[offset+3] = 0;
modifiedPixels++;
}
}
When done, I obtain a new UIImage from the bitmap context and display it in a UIImageView, to see the result:
CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
UIImage *img = [[UIImage alloc] initWithCGImage:imageRef];
Question:
What am I doing wrong?
Is this happening because I modify the data while iterating over it? Must I duplicate it?
Use CGBitmapContextGetBytesPerRow(bitmapContext) to get bytesPerRow instead getting from image (image has only 3 bytes per pixels if it hasn't alpha informations)
Might you're getting wrong height or width.... and by the way 240x40=9600 not 8000 so that's for sure that you're not iterating over each and every pixel.

iPhone App Green Screen Replacement

Q: I'm looking to use the iPhone camera to take a photo and then replace the green screen in that photo with another photo.
What's the best way to dive into this? I couldn't find many resources online.
Thanks in advance!
Conceptually, all that you need to do is loop through the pixel data of the photo taken by the phone, and for each pixel that is not within a certain range of green, copy the pixel into the same location on your background image.
Here is an example I modified from keremic's answer to another stackoverflow question.
NOTE: This is untested and just intended to give you an idea of a technique that will work
//Get data into C array
CGImageRef image = [UIImage CGImage];
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel_ * width;
NSUInteger bitsPerComponent = 8;
unsigned char *data = malloc(height * width * bytesPerPixel);
// you will need to copy your background image into resulting_image_data.
// which I am not showing here
unsigned char *resulting_image_data = malloc(height * width * bytesPerPixel);
CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height));
CGContextRelease(context);
//loop through each pixel
for(int row = 0; row < height; row++){
for(int col = 0; col < width*bytesPerPixel; col=col+4){
red = data[col];
green = data[col + 1];
blue = data[col + 2];
alpha = data[col + 3];
// if the pixel is within a shade of green
if(!(green > 250 && red < 10 && blue < 10)){
//copy them over to the background image
resulting_image_data[row*col] = red;
resulting_image_data[row*col+1] = green;
resulting_image_data[row*col+2] = blue;
resulting_image_data[row*col+3] = alpha;
}
}
}
//covert resulting_image_data into a UIImage
Have a look at compiling OpenCV for iPhone - not an easy task, but it gives you access to a whole library of really great image processing tools.
I'm using openCV for an app I'm developing at the moment (not all that dissimilar to yours) - for what you're trying to do, openCV would be a great solution, although it requires a bit of learning etc. Once you've got OpenCV working, the actual task of removing green shouldn't be too hard.
Edit: This link will be a helpful resource if you do decide to use OpenCV: Compiling OpenCV for iOS

split UIImage by colors and create 2 images

I have looked through replacing colors in an image but cannot get it to work how i need because I am trying to do it with every color but one, as well as transparency.
what I am looking for is a way to take in an image and split out a color (say all the pure black) from that image. Then take that split out portion and make a new image with a transparent background and the split out portion.
(here is just an example of the idea, say i want to take a screenshot of this page. make every other color but pure black be transparent, and save that new image to the library, or put it into a UIImageView)
i have looked in to CGImageCreateWithMaskingColors but cant seem to do what I need with the transparent portion, and I dont really understand the colorMasking input other than you can provide it with a {Rmin,Rmax,Gmin,Gmax,Bmin,Bmax} color mask but when I do, it colors everything. any ideas or input would be great.
Sounds like you're going to have to get access to the underlying bytes and write code to process them directly. You can use CGImageGetDataProvider() to get access to the data of an image, but there's no guarantee that the format will be something you know how to handle. Alternately you can create a new CGContextRef using a specific format you know how to handle, then draw the original image into your new context, then process the underlying data. Here's a quick attempt at doing what you want (uncompiled):
- (UIImage *)imageWithBlackPixels:(UIImage *)image {
CGImageRef cgImage = image.CGImage;
// create a premultiplied ARGB context with 32bpp
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
size_t bpc = 8; // bits per component
size_t bpp = bpc * 4 / 8; // bytes per pixel
size_t bytesPerRow = bpp * width;
void *data = malloc(bytesPerRow * height);
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
CGContextRef ctx = CGBitmapContextCreate(data, width, height, bpc, bytesPerRow, colorspace, bitmapInfo);
CGColorSpaceRelease(colorspace);
if (ctx == NULL) {
// couldn't create the context - double-check the parameters?
free(data);
return nil;
}
// draw the image into the context
CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), cgImage);
// replace all non-black pixels with transparent
// preserve existing transparency on black pixels
for (size_t y = 0; y < height; y++) {
size_t rowStart = bytesPerRow * y;
for (size_t x = 0; x < width; x++) {
size_t pixelOffset = rowStart + x*bpp;
// check the RGB components of the pixel
if (data[pixelOffset+1] != 0 || data[pixelOffset+2] != 0 || data[pixelOffset+3] != 0) {
// this pixel contains non-black. zero it out
memset(&data[pixelOffset], 0, 4);
}
}
}
// create our new image and release the context data
CGImageRef newCGImage = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
free(data);
UIImage *newImage = [UIImage imageWithCGImage:newCGImage scale:image.scale orientation:image.imageOrientation];
CGImageRelease(newCGImage);
return newImage;
}