5

Here's an example of an image I would like to trim. I want to get rid of the borders around the image (in this case the top and bottom black bars).

UIImage to trim

I found a library on Github: CKImageAdditions, however it doesn't seem to work. When I pass in a UIColor (with RGB colours) it just returns the same image.

I can find a lot of examples and category classes that would trim a UIImage with any transparent pixels as the border, but in this case I need to trim the black colour. I have sampled the colour in my images and they the colour value is indeed 255, but it doesn't seem to match what the above library is looking for.

Does anyone have a library they have used or any insight? I've searched and searched and CKImageAdditions has been the only thing I can find that advertises to trim with a colour (although, unfortunately doesn't work in my case).

runmad
  • 14,846
  • 9
  • 99
  • 140
  • Do you know the dimensions of the newly trimmed image, or is this supposed to be dynamic and try whatever color you specify? – Peter Foti Nov 18 '13 at 20:18
  • @PeterFoti I don't care about the dimensions. I put it inside a UIImageView with `UIViewContentModeScaleAspectFill` as the `UIContentMode`. And I only want to trim black borders, I don't care about other colours. Thanks. – runmad Nov 18 '13 at 20:45
  • But are these borders baked into the image, or are they being added after you add it to your view? – Peter Foti Nov 18 '13 at 20:49
  • @PeterFoti That's how an image is given to me, so the borders are there and I want to get rid of them before I display them in the `UIImageView` – runmad Nov 18 '13 at 20:51
  • You can do it manually by scanning image by pixel (row by row) and searching when pixel row average color is not black. If you found it - stop your algorithm because you found first border. Repeat this algorithm for every side of image (for left and right sides you should iterate over pixels column by column instead of rows for top and bottom sides). You can use pixels scanning approach from this answer: http://stackoverflow.com/questions/2342327/how-i-count-red-color-pixel-from-the-uiimage-using-objective-c-in-iphone – opedge Nov 18 '13 at 21:11
  • @opedge Which seems to be what's being done in `CKImageAdditions`, although just doesn't seem to work. – runmad Nov 18 '13 at 21:44

2 Answers2

7

I ended up customizing a method from a function in CKImageAdditions that had this functionality supposedly but I couldn't get it to work. It just wouldn't trim the colour, so I instead check the pixel's RGB values to be \0 (black). The CKImageAdditions just couldn't find the black pixels, unfortunately.

Since the images I wanted to trim sometimes didn't have super black bars (sometimes they'd have a stray pixel with a lighter dark colour or something) I added GPUImage functionality to the method, which basically just creates a black and white version of the image with a strong filter on it so any dark colours become black and any light colours become white, making the black bar borders much more prominent and ensuring better results when I look for them in the method. And of course I crop the original image at the end based on the results from the black/white image.

Here's my code:

typedef struct Pixel { uint8_t r, g, b, a; } Pixel;

+(UIImage*)trimBlack:(UIImage*)originalImage {
    GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:originalImage];
    GPUImageLuminanceThresholdFilter *stillImageFilter = [[GPUImageLuminanceThresholdFilter alloc] init];
    stillImageFilter.threshold = 0.1;
    [stillImageSource addTarget:stillImageFilter];
    [stillImageSource processImage];
    UIImage *imageToProcess = [stillImageFilter imageFromCurrentlyProcessedOutput];

    RMImageTrimmingSides sides = RMImageTrimmingSidesAll;
    CGImageRef image = imageToProcess.CGImage;
    void * bitmapData = NULL;
    CGContextRef context = CKBitmapContextAndDataCreateWithImage(image, &bitmapData);

    Pixel *data = bitmapData;

    size_t width = CGBitmapContextGetWidth(context);
    size_t height = CGBitmapContextGetHeight(context);

    size_t top = 0;
    size_t bottom = height;
    size_t left = 0;
    size_t right = width;

    // Scan the left
    if (sides & RMImageTrimmingSidesLeft) {
        for (size_t x = 0; x < width; x++) {
            for (size_t y = 0; y < height; y++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    left = x;
                    goto SCAN_TOP;
                }
            }
        }
    }

    // Scan the top
SCAN_TOP:
    if (sides & RMImageTrimmingSidesTop) {
        for (size_t y = 0; y < height; y++) {
            for (size_t x = 0; x < width; x++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    top = y;
                    goto SCAN_RIGHT;
                }
            }
        }
    }

    // Scan the right
SCAN_RIGHT:
    if (sides & RMImageTrimmingSidesRight) {
        for (size_t x = width-1; x >= left; x--) {
            for (size_t y = 0; y < height; y++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    right = x;
                    goto SCAN_BOTTOM;
                }
            }
        }
    }

    // Scan the bottom
SCAN_BOTTOM:
    if (sides & RMImageTrimmingSidesBottom) {
        for (size_t y = height-1; y >= top; y--) {
            for (size_t x = 0; x < width; x++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    bottom = y;
                    goto FINISH;
                }
            }
        }
    }

FINISH:
    CGContextRelease(context);
    free(bitmapData);

    CGRect rect = CGRectMake(left, top, right - left, bottom - top);
    return [originalImage imageCroppedToRect:rect];
}

Thanks to all the hard work from the developers of the libraries used in the code above and of course all credit goes to them!

runmad
  • 14,846
  • 9
  • 99
  • 140
  • Hey, if anyone is reading this later: this doesn't work if you want to trim a white border out of an image. Even if you're changing all the '\0' with '\255'. (Also, to try this code you need CKImageAdditions & UIImage+FX) – Benjamin Netter Feb 28 '14 at 15:03
1

I tweaked your solution now that GPUImage's imageFromCurrentlyProcessedOutput method has been removed and replaced with another method that doesn't work at all.

Also tweaked how the image was cropped and removed some stuff that was just broken. Seems to work.

typedef struct Pixel { uint8_t r, g, b, a; } Pixel;

-(UIImage*)trimBlack:(UIImage*)originalImage {
    GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:originalImage];
    GPUImageLuminanceThresholdFilter *stillImageFilter = [[GPUImageLuminanceThresholdFilter alloc] init];
    stillImageFilter.threshold = 0.1;
    [stillImageSource addTarget:stillImageFilter];
    [stillImageSource processImage];
    [stillImageSource useNextFrameForImageCapture];
    
    //UIImage *imageToProcess = [stillImageFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
    //UIImage *imageToProcess = [UIImage imageWithCGImage:[stillImageFilter newCGImageFromCurrentlyProcessedOutput]];
    UIImage *imageToProcess = originalImage;
    
    //RMImageTrimmingSides sides = RMImageTrimmingSidesAll;
    CGImageRef image = imageToProcess.CGImage;
    void * bitmapData = NULL;
    CGContextRef context = CKBitmapContextAndDataCreateWithImage(image, &bitmapData);
    
    Pixel *data = bitmapData;
    
    size_t width = CGBitmapContextGetWidth(context);
    size_t height = CGBitmapContextGetHeight(context);
    
    size_t top = 0;
    size_t bottom = height;
    size_t left = 0;
    size_t right = width;
    
    // Scan the left
    //if (sides & RMImageTrimmingSidesLeft) {
        for (size_t x = 0; x < width; x++) {
            for (size_t y = 0; y < height; y++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    left = x;
                    goto SCAN_TOP;
                }
            }
        }
    //}
    
    // Scan the top
SCAN_TOP:
    //if (sides & RMImageTrimmingSidesTop) {
        for (size_t y = 0; y < height; y++) {
            for (size_t x = 0; x < width; x++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    top = y;
                    goto SCAN_RIGHT;
                }
            }
        }
    //}
    
    // Scan the right
SCAN_RIGHT:
    //if (sides & RMImageTrimmingSidesRight) {
        for (size_t x = width-1; x >= left; x--) {
            for (size_t y = 0; y < height; y++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    right = x;
                    goto SCAN_BOTTOM;
                }
            }
        }
    //}
    
    // Scan the bottom
SCAN_BOTTOM:
    //if (sides & RMImageTrimmingSidesBottom) {
        for (size_t y = height-1; y >= top; y--) {
            for (size_t x = 0; x < width; x++) {
                Pixel pixel = data[y * width + x];
                if (pixel.r != '\0' && pixel.g != '\0' && pixel.b != '\0') {
                    bottom = y;
                    goto FINISH;
                }
            }
        }
    //}
    
FINISH:
    CGContextRelease(context);
    free(bitmapData);
    
    CGRect rect = CGRectMake(left, top, right - left, bottom - top);
    CGImageRef imageRef = CGImageCreateWithImageInRect([originalImage CGImage], rect);
    UIImage *croppedImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    return croppedImage;
}
Dave Cole
  • 2,446
  • 2
  • 20
  • 26