Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert Text to Image in Cocoa Objective-C

I'm looking for a way to convert a piece of text to an image in Cocoa. Everything seems to describe converting an image to text not text to an image.

Simply put, I would like to take a word (such as "Kevin") and convert it into a bitmap image to manipulate and save as a JPEG.

The people giving answers are awesome. Thank you for three different and equally valid ways to do it(yes i've tested them)....Very cool I wish I could give you all correct answers.

like image 473
Kevrone Avatar asked Jul 11 '12 23:07

Kevrone


4 Answers

EDIT: I misread the question and assumed you wanted Cocoa-touch code (I've left it at the end in case you did). Here is one way of doing it in Cocoa using CoreText (as another poster says there are a bunch of ways):

{
    NSString* string = @"Kevin";
    CGFloat fontSize = 12.0f;

    // Create an attributed string with string and font information
    CTFontRef font = CTFontCreateWithName(CFSTR("Helvetica Light"), fontSize, nil);
    NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                (id)font, kCTFontAttributeName, 
                                nil];
    NSAttributedString* as = [[NSAttributedString alloc] initWithString:string attributes:attributes];
    CFRelease(font);

    // Figure out how big an image we need 
    CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)as);
    CGFloat ascent, descent, leading;
    double fWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

    // On iOS 4.0 and Mac OS X v10.6 you can pass null for data 
    size_t width = (size_t)ceilf(fWidth);
    size_t height = (size_t)ceilf(ascent + descent);
    void* data = malloc(width*height*4);

    // Create the context and fill it with white background
    CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast;
    CGContextRef ctx = CGBitmapContextCreate(data, width, height, 8, width*4, space, bitmapInfo);
    CGColorSpaceRelease(space);
    CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0); // white background
    CGContextFillRect(ctx, CGRectMake(0.0, 0.0, width, height));

    // Draw the text 
    CGFloat x = 0.0;
    CGFloat y = descent;
    CGContextSetTextPosition(ctx, x, y);
    CTLineDraw(line, ctx);
    CFRelease(line);

    // Save as JPEG
    CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
    NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage:imageRef];
    NSAssert(imageRep, @"imageRep must not be nil");
    NSData* imageData = [imageRep representationUsingType:NSJPEGFileType properties:nil];
    NSString* fileName = [NSString stringWithFormat:@"Kevin.jpg"];
    NSString* fileDirectory = NSHomeDirectory();
    NSString* filePath = [fileDirectory stringByAppendingPathComponent:fileName];
    [imageData writeToFile:filePath atomically:YES];

    // Clean up
    [imageRep release];
    CGImageRelease(imageRef);
    free(data);
}

This is the cocoa-touch version:

// Figure out the dimensions of the string in a given font.
NSString* kevin = @"Kevin";
UIFont* font = [UIFont systemFontOfSize:12.0f];
CGSize size = [kevin sizeWithFont:font];
// Create a bitmap context into which the text will be rendered.
UIGraphicsBeginImageContext(size);
// Render the text 
[kevin drawAtPoint:CGPointMake(0.0, 0.0) withFont:font];
// Retrieve the image
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
// Convert to JPEG
NSData* data = UIImageJPEGRepresentation(image, 1.0);
// Figure out a safe path
NSArray *arrayPaths = NSSearchPathForDirectoriesInDomains(
                                    NSDocumentDirectory,
                                    NSUserDomainMask,
                                    YES);
NSString *docDir = [arrayPaths objectAtIndex:0];
// Write the file
NSString *filePath = [docDir stringByAppendingPathComponent:@"Kevin.jpg"];
BOOL success = [data writeToFile:filePath atomically:YES];
if(!success)
{
    NSLog(@"Failed to write to file. Perhaps it already exists?");
}
else
{
    NSLog(@"JPEG file successfully written to %@", filePath);
}
// Clean up
UIGraphicsEndImageContext();

When I made started iOS programming I found the following things unintuitive or unusual. The methods to measure and draw strings are methods on NSString (not the graphics context as in other systems). The method to save the data is a method on NSData not a file class! The functions to create the graphics context are plain C functions and not part of any class.

Hope this helps!

like image 137
idz Avatar answered Nov 11 '22 09:11

idz


There are, (fortunately or unfortunately), a number of different ways to do this.

Version 1: AppKit/Foundation only

NSString *text = ...;
NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSFont fontWithName:@"Helvetica" size:24], NSFontAttributeName,
    nil];
NSImage *img = [[NSImage alloc] initWithSize:NSMakeSize(250, 250)];
[img lockFocus];
[text drawAtPoint:NSMakePoint(10, 10) withAttributes:attr];
[img unlockFocus];

// when you want to write it to a JPEG
NSData *dat = [NSBitmapImageRep
    representationOfImageRepsInArray:[img representations]
    usingType:NSJPEGFileType
    properties:[NSDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithFloat:0.9], NSImageCompressionFactor,
        nil]];

Then you can write dat to a file as you see fit.

Version 2:

The same can be accomplished using CGContextRef (creating a bitmap context), and the equivalent Quartz APIs. This obviates the need for Objective C, but the code will be a little longer as a result. You can also use various mixtures of the Quartz (CGxxx) and AppKit (NSxxx) APIs, but the Quartz APIs are generally more cumbersome to use (because of their flexibility wrt. allocation and other concerns).

Version 3:

You can also use Quartz + Core Text, which is OS X 10.5+. This allows you a lot of flexibility in terms of exactly how your text is laid out, and also provides a relatively easy way to measure how big the text is before you draw it to a bitmap (so you can make the bitmap large enough).

Footnote: Things like skew are easy enough to apply before you draw the text. The text can be drawn with skew (see NSAffineTransform and the Cocoa drawing guide).

like image 38
Dietrich Epp Avatar answered Nov 11 '22 09:11

Dietrich Epp


I believe the function you want is CGContextShowTextAtPoint().

Example usage:

NSString *input = /* ... */;
CGContextRef context = /* create a graphics context */;

// make sure you have set up the font
CGContextShowTextAtPoint(context, 5, 5, [input UTF8String], [input length]);
like image 40
Richard J. Ross III Avatar answered Nov 11 '22 09:11

Richard J. Ross III


Here's a minimal command line tool that does what you described. Pass it the path where you want save the result, e.g:

"./test foo.tiff"

#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {
      NSString *string = @"Hello, World!";
      NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:1];

      NSDictionary *attributes =
        @{ NSFontAttributeName : [NSFont fontWithName:@"Helvetica" size:40.0],
        NSForegroundColorAttributeName : NSColor.blackColor};

      NSImage *image = [[NSImage alloc] initWithSize:[string sizeWithAttributes:attributes]];
      [image lockFocus];
      [string drawAtPoint:NSZeroPoint withAttributes:attributes];
      [image unlockFocus];
      [[image TIFFRepresentation] writeToFile:path atomically:YES];
    }
    return 0;
}
like image 32
NSResponder Avatar answered Nov 11 '22 07:11

NSResponder