如何在Cocoa Objective-C中将文本转换为图像

5
我正在寻找一种在Cocoa中将文本转换为图像的方法。所有的描述都似乎是将图像转换为文本而不是将文本转换为图像。
简单地说,我想把一个单词(比如"Kevin")转换成位图图像,以便进行处理并保存为JPEG格式。
给出答案的人很棒。谢谢你们提供了三种不同但同样有效的方法(是的,我已经测试过它们)...非常酷,我希望我能把正确答案都给你们。

@hypercrypt 无效的编辑。这是关于Cocoa,而不是Cocoa Touch。已回滚。 - Richard J. Ross III
作为一个既有Cocoa又有Windows GDI文本经验的人,我必须说,相比之下,Windows GDI方法(ScriptItemize -> ScriptShape -> ScriptPlace -> 等等)需要编写大约3倍的代码才能实现与Cocoa相同的功能。(虽然GDI不是最新的API,但仍然如此。) - Dietrich Epp
实际上,使用C#,您只需创建一个位图,位图对象具有在x,y坐标处编写文本的内置功能。这非常简单。 - Kevrone
以下是C#风格的代码:Bitmap myBitmap = new Bitmap("C:\myImage.jpg");Graphics g = Graphics.FromImage(myBitmap);g.DrawString("我的\n文本", new Font("Tahoma", 40), Brushes.White, new PointF(0, 0)); - Kevrone
@Richard J. Ross III 不好意思,我在编辑时将其标记为iOS... - hypercrypt
4个回答

8

编辑:我误读了问题,并假设您想要Cocoa-touch代码(如果是这样,我将在末尾保留它)。以下是使用CoreText在Cocoa中完成此操作的一种方法(正如其他帖子所述,还有许多其他方法):

{
    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);
}

这是Cocoa Touch版本:
// 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();

当我开始学习iOS编程时,我发现以下几点很不直观或者不寻常。测量和绘制字符串的方法是在NSString上而不是像其他系统一样在图形上下文中。保存数据的方法是在NSData上而不是文件类!创建图形上下文的函数是普通的C函数而不是任何类的一部分。
希望这能有所帮助!

1
如果这是为iPhone开发的就太好了,但原始问题标记的是Cocoa而不是Cocoa Touch。 - Richard J. Ross III
虽然这三个例子都很好,但这个例子将它放在了新手的复制粘贴水平上,尽管我相信有更简单的方法来编写它。 - Kevrone
这不会产生一个精确的边界框。例如,尝试使用 y,你会发现在顶部有太多的空间。 - user187676
如果您想获取精确的边界框,请使用CTLineGetImageBounds,请参见:https://developer.apple.com/reference/coretext/1510967-ctlinegetimagebounds?language=objc - idz

5

幸运或不幸的是,有多种不同的方法来实现这一点。

版本1:仅限于AppKit/Foundation。

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]];

然后,您可以根据需要将dat写入文件。
版本2:
使用CGContextRef(创建位图上下文)和等效的Quartz API也可以实现相同的功能。 这样可以避免使用Objective C,但结果代码会更长一些。 您还可以使用Quartz(CGxxx)和AppKit(NSxxx)API的各种混合,但是由于其灵活性(关于分配和其他问题),Quartz API通常更加繁琐。
版本3:
您还可以使用Quartz + Core Text,它适用于OS X 10.5+。 这使您在准确布局文本方面具有很大的灵活性,并且还提供了一个相对容易的方法来测量在将其绘制到位图之前文本的大小(因此您可以使位图足够大)。 注:像倾斜这样的效果可以在绘制文本之前很容易地应用。文本可以使用倾斜来绘制(参见NSAffineTransformCocoa绘图指南)。

这个应该被视为正确答案。 - Jiulong Zhao

4
我相信你想要的功能是 CGContextShowTextAtPoint()
使用示例:
NSString *input = /* ... */;
CGContextRef context = /* create a graphics context */;

// make sure you have set up the font
CGContextShowTextAtPoint(context, 5, 5, [input UTF8String], [input length]);

这能让我将它保存为图像吗? - Kevrone
通常情况下,您应该使用更高级别的API,因为CGContextShowTextAtPoint()存在许多已知的缺陷。一个例子,和另一个例子 - Kurt Revis
不需要降到CG级别。NSString和NSAttributedString都有绘图方法。 - NSResponder
该死的NSResponder是正确的。但是你仍然需要一个上下文来绘制它。 - Kevrone
最简单的方法是创建一个NSImage实例来绘制某个东西。 - NSResponder

2
这是一个最简单的命令行工具,可以实现您所描述的功能。将要保存结果的路径传递给它,例如:

"./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;
}

很遗憾,我不知道命令行和窗口程序之间是否有区别,因为我来自M$世界,但这似乎可以编译通过,但会抛出上下文错误。 - Kevrone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接