Core Text Tutorial

Author: Eva Diaz-Santana @evdiasan

Introduction

As promised in one of the previous articles on Core Graphics, I want to talk about the wonderful Core Text. Consider the topic as a followup of the previous articles on Core Graphics. If you are new and would rather start with Core Graphics first, you can do so here. And for all, enjoy!

When I first learned Core Text, I thought it was such a great and powerful technology that I was convinced it was already used in many different applications. Unfortunately, I was wrong. And more, I was astonished when I found out that the digital press industry that should be taking the most of it was hardly using it. So, after reviewing the current world press applications published in the AppStore, I started to believe that only a small bunch of people understood the potential of Core Text. Magazines as Wired, newspapers as El Mundo or the so discussed The Daily are not great at all. I mean, there is such a poor design work behind these and other newspapers and magazines that it’s sad to believe how so many designers, creatives and experts cannnot put together something “usable”. Yes, because this is the main problem. A PDF reader is too far away from what a well done iPad or iPhone application can provide.

When the iPad appeared on the market, the press industry started to criticize saying that it wouldn’t have any impact on them. They could not be more wrong. After a while, the press industry realized that the iPad could be the solution to their current crisis: people prefer to read the news in the Internet for free more than in a newspaper. So, the iPad represented the answer to the problem and they began to launch the digital editions of their magazines just to have some presence in this market, just in case. But, the easiest thing to do was simply to copy what others had already done: scan the paper magazine pages and, voilà, the digital version of the magazine was ready to go.

Publishers haven’t understood the real truth: the iPad is a completely new communication channel with lots of capabilities and, as every new technology, it deserves a little bit of research and a hint of creativity. However, under the digital rush to the iPad, we’ve seen terrible mistakes: 500 MB magazines that are so slow and heavy to download, very slow and completely unintuitive user interfaces and, in some of them the simple page zoom is not allowed, and the icing on the cake, newspapers created exclusively for the iPad… But what does it exactly mean? Well, it only means that they do not print on paper, while the rest is just another “old-fashion digitized newspaper” and you can consider yourself lucky if the app does not crash in the middle of your reading.

I don’t really understand why many publishers keep investing so little or so badly in technology to come up with the umpteenth digital publication of a newspaper. What I really wanted to say is that to create a great iPad or iPhone app, the developer has to use the best possible technology and for magazines and newspapers, they should consider seriously Core Text.

Core Text is a C-based API available iOS from version 3.2 designed to ease the handling of advance text layout and font rendering requirements. If you are already familiar with OS X, you may know that it has been part of this OS for years. So, we are not talking about a completely new API.

The Core Text framework is a low-level technology which Apple defines as a text drawing engine. Core Text is not meant to replace the Cocoa text system. If you can deal with high-level constructs, you’d better use Cocoa. So, for example, if you need to display small amounts of text, just use UITextField. If you need to display a larger text, use UITextView. You don’t need much code and is easier to implement.

At this point, you could ask me: “Hey, wait a second! Why should I use Core Text for, if Cocoa offers me plenty of easier solutions to handle text?”. Well, you should be using Core Text, if you need to render text in a very special way (for example, around or inside a shape) or to import your fonts or if you want to draw mixed-style text. You should also consider using Core Text, when you want to display web content and the UIWebView class is a real limitation of what you want to achieve.

In those cases, you will need to dig into the Core Text framework and that’s exactly what I want to show you here. Core Text provides different levels of usage, meaning that it comes with high-level objects, which allow you to draw simple things in an easy way, but it has also low-level objects, which allow you to draw complex text composition and are harder to use.

1. Jumping on it

So, let’s start with the simplest one, which lets you draw individual text lines. To accomplish this, you have to create an attributed string and then just draw the line in the context. An attributed string consists of a string and some metadata describing formatting attributes for portions of the string. In the iOS, attributed strings are represented by the Objective-C class NSAttributedString and the Core Foundation type CFAttributedStringRef. These are toll-free bridged to one another, so you can use them interchangeably.

You will need to create a dictionary to store the attributes you want to apply to the string and then, combine the dictionary with a string generating a NSAttributedString.

Here the steps:

  1. Launch Xcode and create a new view-based iPad project. Name it FunnyText.
  2. Add the Core Text framework to your project.
  3. Import the Core Text header in this view controller.

Open the FunnyTextViewController.xib and change the Class Name of the View object to CustomView.

Go back to Xcode and generate an Objective-C class, subclass of UIView and name it Custom View. In this way, the view of the view controller FunnyTextViewController is your custom view and you can now use Core Graphics to render what you need (in this case a simple line). Import the Core Text header in this class:

#import <CoreText/CoreText.h>

In the CustomView class, create an outlet and its property for an NSAttributedString *attString. Don’t forget to synthesize it. Then, in the CustomView.m edit the drawRect: method as follows:

[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext(); // 1-1

// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity); // 2-1
CGContextTranslateCTM(context, 0, self.bounds.size.height); // 3-1
CGContextScaleCTM(context, 1.0, -1.0); // 4-1

CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attString); // 5-1

// Set text position and draw the line into the graphic context
CGContextSetTextPosition(context, 30.0, 500.0); // 6-1
CTLineDraw(line, context); // 7-1
CFRelease(line); // 8-1

Here, we call the drawRect: on the super class to let it do all the setups. Then, we get a reference to the current graphic context to draw a line on which the text will be displayed.

Now, it is important to know that CoreText uses a reference coordinate system with the origin on the lower-left corner of the screen with the x growing from left to right and the y growing from bottom to top. So, if we just draw some text, it will appear upside down. To avoid that, we need to reverse the coordinate system. This is accomplished by the transformation generated by the lines 2-1, 3-1 and 4-1.

Line 5-1 creates a text line from the attributed string that we will create later. Line 6-1 fixes the text position and finally, we draw the text line and release it.

Now, go to the FunnyTextViewController.m and import the CustomView header:

#import "CustomView.h"

Modify the -viewDidLoad method as follows:

[super viewDidLoad];

CTFontRef fontRef = 
    CTFontCreateWithName((CFStringRef)@"Chalkduster", 36.0f, NULL); // 9-1
NSDictionary *attrDictionary = [NSDictionary dictionaryWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)[[UIColor blueColor] CGColor], (NSString *)(kCTForegroundColorAttributeName), (id)[[UIColor redColor] CGColor], (NSString *) kCTStrokeColorAttributeName, (id)[NSNumber numberWithFloat:-3.0], (NSString *)kCTStrokeWidthAttributeName, nil]; // 10-1
CFRelease(fontRef); // 11-1

NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Everybody loves iNVASIVECODE!" attributes:attrDictionary]; // 12-1
[(CustomView *)[self view] setAttString:attString]; // 13-1
[attString release]; // 14-1

Here, we first create a reference to a font. We used the Chalkduster with size 36.0.

Now, we need to create the attributed string. First, we create a dictionary of attributes (Line 10-1). In this particular case, we set the font, the text color, the stroke color and the stroke width. Notice that the stroke width should be negative for stroke and fill and positive only for stroke. Check the Core Text String Attribute Reference, if you want to apply additional customizations. Finally, we apply the attributes to the string using the initWithString:attributes: method (Line 12-1).

Line 13-1 passes the created attributed string to the custom view and line 14-1 releases the attributed string, since we created with a alloc/init pattern.

Wonderful, we are done. Now, just build and go and if you did not make any mistake you should see the following result:

1

2. Writing on shapes

In the previous chapter, you saw how to create a NSAttributedString object, which is a string with a set of attributes, and draw it in a graphic context. You also noticed that the used method was limited to draw only one text line each time and, depending on the length of the text line, the text could draw outside the graphic context. So, how do we deal with a long text? That’s exactly what we are going to explore here: we are going to draw multiple lines in our graphic context.

Core Text allows you to draw a multi-line paragraph within an arbitrarily sized rectangular area very easily. You have to define again an attributed text string and a rectangle to provide the display area in the current graphic context.

Start a new view-based iPad project and follow the steps described in the previous chapter. The difference is represented by few lines of code we are going to see now.

CUSTOM DRAWING

In the drawRect: method, you get a reference to the current graphic context using the UIGraphicsGetCurrentContext function. Then, you reverse the coordinate system to avoid the text to appear upside down (Lines 1-1 to 4-1 from Part 1 remain the same). After that, replace the rest of the code with the following lines of code:

CGMutablePathRef path = CGPathCreateMutable(); // 5-2
CGPathAddRect(path, NULL, CGRectMake(200.0, 300.0, 400.0, 600.0)); // 6-2

Here, I just created a mutable path (a rectangle). We need this to define the text frame. Additionally, we need to create a framesetter that uses the attributed string and the path to generate the text frame.

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString); // 7-2
CTFrameRef theFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL); // 8-2
CFRelease(framesetter); // 9-2
CFRelease(path); // 10-2

Finally, we draw the text:

CTFrameDraw(theFrame, context); // 11-2
CFRelease(theFrame); // 12-2

Remember to release all the objects allocated in memory (Lines 9-2, 10-2 and 12-2).

Now, a CTFramesetter is used to generate a text frame. It takes the attributed string object and a shape descriptor object and calls into the typesetter to create line objects that fill that shape. The output is a CTFrame object containing an array of lines. The frame can then draw itself directly into the current graphic context.

IN THE VIEW CONTROLLER

In this viewDidLoad method, we are going to create the attributed string as we did in Core Text Part I. The steps are exactly the same, but I am going to modify a few things:

  • To give a sense to this example, you need a long text. Just, copy some text from somewhere. Instead, I used a small Mac OS application named LittleIpsum, which is a Latin Generator Software and works really great for this.
  • I changed the attributes associated to the string. I want to use a simpler layout and I like to play with different fonts and sizes.

Let’s write the dictionary containing the attributes, since that’s essentially the only thing that is going to change. The font used is Didot and the size is 30.0f:

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"Didot", 30.0f, NULL); // 1-2
NSDictionary *attrDictionary = [NSDictionary dictionaryWithObjectsAndKeys: (id)fontRef, (NSString *)kCTFontAttributeName, (id)[[UIColor blackColor] CGColor], (NSString *)(kCTForegroundColorAttributeName), nil]; // 2-2
CFRelease(fontRef); // 3-2

Then, apply the attributes to the string using the initWithString:attributes: method (Line 12-1 from Part 1), where you should replace the existing text by a longer text. Line 13-1 from Part I passes the created attributed string to the custom view and line 14-1 from Part I releases the attributed string, since we created with a +alloc/-init pattern.

That’s it. Just build and you should see the following result:

2

I’m guessing that at this point you are not impressed. I know you are thinking: “We wrote a lot of code to obtain something so relatively simple?”. However, do not underestimate this exercise, because with the basic knowledge you acquired and some creativity from your side, you are ready to create things like the ones I’m going to show you here.

3

Here, the rectangle was replaced by a circle. Very easy, right? Just append a circle to the mutable graphic path, instead of a rectangle and you are done!

In the next pictures, I defined multiple rectangles to create the well-known newspaper column layout, with as many columns as you like. Note the text is justified.

4

5

3. Details in the text

I’m sure you have noticed that in all our examples we have been using a single set of attributes for the whole text, so all the text contained in the shape defined by the CGPath had the same font and color. What happens when we need to edit rich text inside the CGPath? I’m going to show you how you can mix and match fonts and colors!

Open again the Xcode project previously created. Now, we want to create different groups of attributes (font, color, etc.) in the viewDidLoad method in your view controller. Modify that method as follows (see in bold the modifications to the existing method):

[super viewDidLoad];

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"Palatino", 20.0f, NULL); // 1-3
NSDictionary *attrDictionary = [NSDictionary dictionaryWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)[[UIColor blackColor] CGColor], (NSString *)(kCTForegroundColorAttributeName), nil];
CFRelease(fontRef);

NSAttributedString *attString = [[NSAttributedString alloc] initWithString:@"Sed posuere consectetur est at lobortis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Donec sed odio dui.\r\nMaecenas sed diam eget risus varius blandit sit amet non magna. Sed posuere consectetur est at lobortis. Aenean lacinia bibendum nulla sed consectetur. Nullam id dolor id nibh ultricies vehicula ut id elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.\r\n Sed posuere consectetur est at lobortis. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Donec id elit non mi porta gravida at eget metus. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.\r\n Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum. Maecenas sed diam eget risus varius blandit sit amet non magna." attributes:attrDictionary]; // 2-3

CTFontRef fontRefBold = CTFontCreateWithName((CFStringRef)@"Palatino-Bold", 20.0f, NULL); // 3-3
NSDictionary *attrDictionaryBold = [NSDictionary dictionaryWithObjectsAndKeys:(id)fontRefBold, (NSString *)kCTFontAttributeName, (id)[[UIColor blackColor] CGColor], (NSString *)(kCTForegroundColorAttributeName), nil]; // 4-3
[attString addAttributes:attrDictionaryBold range:NSMakeRange(40,85)]; // 5-3
CFRelease(fontRefBold); // 6-3

[(CustomView *)[self view] setAttString:attString];
[attString release];

Line 1-3 and 2-3 are modifications to the existing code. In those two line we are defining that a new font type and size, and we are increasing the length of text.

The following lines should look very familiar to you, because we are starting over the process of creating a reference to a font (“Palatino-Bold”) (Line 3-3) and creating a dictionary of attributes (Line 4-3).

Line 5-3 is what brings some novelty to our code, defining the range of characters inside the string where the new attributes should be applied. Let’s also change the dimensions of the rectangle that will contain our text. In the drawrect: method, change the dimensions as follows:

CGPathAddRect(path, NULL, CGRectmake(100.0, 300.0, 600.0, 600.0));

Now, if you build and run, you should get something similar to the following result.

6

Note that you can repeat the code from lines 3-3 to 6-3 to add as many attributes as you like, creating results as shown below:

7

8

I would also like to point out that the method I have shown you is not very practical: counting characters is not something that can be done extensively. But what I have shown you is only one simple method. Depending on the data and how they are structured, you could think about applying other methods too. Take a look at the following method:

(void)appendAttributedString:(NSAttributedString *)attributedString

This methods would allow to append attributed strings creating a single attributed string.

4. Rendering on a curved shape

Now, let’s explore how to draw our text on a curved shape.

Open the Xcode project created until now. As usual, we are going to modify the existing code, so that we don’t have to repeat the same steps. Let’s define the different attributes groups (font, color, etc.) in the viewDidLoad of the view controller.

Modify the viewDidLoad method as follows (see in bold the modifications to the existing method):

[super viewDidLoad];

CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"Arial Rounded MT Bold", 125.0f, NULL); // 1-4
NSDictionary *attrDictionary = [NSDictionary dictionaryWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, nil];
CFRelease(fontRef);

NSAttributedString *attString=[[NSAttributedString alloc] initWithString:@"iNVASIVECODE" attributes:attrDictionary]; // 2-4
[(CustomView *)[self view] setAttString:attString];
[attString release];

Line 1-4 and 2-4 are modifications to the existing code. In those two lines, we are defining that a new font type and size, and we are increasing the length of text. Before we can go on, we have to clarify some concept that we are going to need later:

A character is the smallest unit of written language that carries meaning, e.g. uppercase A.

A glyph is a concrete form of a character. In our previous example, the character uppercase A can be drawn with a different size or a different stroke thickness. It can lean or be vertical, and have certain optional variations in form. The following figure represents glyphs of the character A:

Example Glyphs

Note that characters and glyphs do not have a one-to-one correspondence. In some cases, a character may be represented by multiple glyphs, such as an é, which may be an e glyph combined with an acute accent glyph ´ (accent). In other cases, a single glyph may represent multiple characters, as in the case of a ligature, or joined letter. The following figure represents the ligature case:

Example of Glyphs

A glyph-run is a set of consecutive glyphs sharing the same attributes and direction.

Now, let’s go now to the drawrect: method, where we are going to draw our text along a semi-circle. Since there are many lines of code, I’m going to group them by task.

Line 3-4 calls the drawRect: on the super class to let it do all the setups. Then, line 4-4 gets a reference to the current graphic context. Lines 5-4 to 7-4 flip the coordinate system, so our text doesn’t appear upside down and finally, line 8-4 adds a rotation of 90 degrees counterclockwise.

[super drawRect:rect]; // 3-4

CGContextRef context = UIGraphicsGetCurrentContext(); // 4-4
CGContextSetTextMatrix(context, CGAffineTransformIdentity); // 5-4

CGContextTranslateCTM(context, self.bounds.size.width/2, self.bounds.size.height/2); // 6-4
CGContextScaleCTM(context, 1.0, -1.0); // 7-4
CGContextRotateCTM(context, M_PI_2); // 8-4

Line 9-4 creates a text line from the attributed string. Line 10-4 extracts the number of glyphs and line 11-4 the number of runs. At this point, you should already know the numbers that will come up. Try guessing and see if you understood the concepts previously explained (characters, glyphs and runs): how many glyphs and how many glyph-runs exist in the text “iNVASIVECODE”?

CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)theString);// 9-4
CFIndex glyphCount = CTLineGetGlyphCount(line); // 10-4
CFArrayRef runArray = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(runArray); // 11-4

Now, we need to create an array to store the width of each glyph. Line 12-4 is where the array is created and then after running through the run glyphs, line 13-4 stores the different widths of each glyph.

NSMutableArray *widthArray = [[NSMutableArray alloc] init]; // 12-4

CFIndex glyphOffset = 0;
for (CFIndex i = 0; i < runCount; i++) {
    CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, i);
    CFIndex runGlyphCount = CTRunGetGlyphCount((CTRunRef)run);
    for (CFIndex runGlyphIndex = 0; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
        NSNumber *widthValue = [NSNumber numberWithDouble:CTRunGetTypographicBounds((CTRunRef)run, CFRangeMake(runGlyphIndex, 1), NULL, NULL, NULL)];
        [widthArray insertObject:widthValue atIndex:(runGlyphIndex + glyphOffset)];  // 13-4
    }
    glyphOffset = runGlyphCount + 1;
}

We have to divide the length of the line where we are going to draw the text into slices. Each slice has to cover the distance from one glyph’s center to the next. In our case, we are going to store the angles of the semi-circle into an array. Line 14-4 creates a new array and line 15-4 stores the angles.

CGFloat lineLength = CTLineGetTypographicBounds(line, NULL, NULL, NULL);

NSMutableArray *angleArray = [[NSMutableArray alloc] init]; // 14-4

CGFloat prevHalfWidth =  [[widthArray objectAtIndex:0] floatValue] / 2.0;
NSNumber *angleValue = [NSNumber numberWithDouble:(prevHalfWidth / lineLength) * M_PI];
[angleArray insertObject:angleValue atIndex:0];

for (CFIndex lineGlyphIndex = 1; lineGlyphIndex < glyphCount; lineGlyphIndex++) {
    CGFloat halfWidth = [[widthArray objectAtIndex:lineGlyphIndex] floatValue] / 2.0;
    CGFloat prevCenterToCenter = prevHalfWidth + halfWidth;
    NSNumber *angleValue = [NSNumber numberWithDouble:(prevCenterToCenter / lineLength) * M_PI];
    [angleArray insertObject:angleValue atIndex:lineGlyphIndex]; // 15-4
    prevHalfWidth = halfWidth;
}

Let’s do the actual drawing. We have previously calculated the angle offset for each glyph relative to the previous glyph, so let’s draw them rotating the context in line 16-4 after each glyph so they appear spread along a semi-circular path. Line 17-4 centers this glyph by moving left by half its width and offset text position leftwards by this glyph’s width in preparation for the next glyph.

CGPoint textPosition = CGPointMake(0.0, 250.0);
CGContextSetTextPosition(context, textPosition.x, textPosition.y);

glyphOffset = 0;
for (CFIndex runIndex = 0; runIndex < runCount; runIndex++) {
    CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
    CFIndex runGlyphCount = CTRunGetGlyphCount(run);
    CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
    for (CFIndex runGlyphIndex = 0; runGlyphIndex < runGlyphCount; runGlyphIndex++) {

        CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);

        CGContextRotateCTM(context, -[[angleArray objectAtIndex:(runGlyphIndex + glyphOffset)] floatValue]); // 16-4

        CGFloat glyphWidth = [[widthArray objectAtIndex:(runGlyphIndex + glyphOffset)] floatValue];
        CGFloat halfGlyphWidth = glyphWidth / 2.0;
        CGPoint positionForThisGlyph = CGPointMake(textPosition.x - halfGlyphWidth, textPosition.y); // 17-4

        textPosition.x -= glyphWidth;

        CGAffineTransform textMatrix = CTRunGetTextMatrix(run);
        textMatrix.tx = positionForThisGlyph.x; textMatrix.ty = positionForThisGlyph.y;
        CGContextSetTextMatrix(context, textMatrix);

        CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
        CGGlyph glyph; CGPoint position;
        CTRunGetGlyphs(run, glyphRange, &glyph);
        CTRunGetPositions(run, glyphRange, &position);

        CGContextSetFont(context, cgFont);
        CGContextSetFontSize(context, CTFontGetSize(runFont));

        CGContextSetRGBFillColor(context, 0.9, 0.9, 0.9, 1.0);
        CGContextShowGlyphsAtPositions(context, &glyph, &position, 1);

        CFRelease(cgFont);
    }
    glyphOffset += runGlyphCount;
}

Don’t forget to release the objects.

CFRelease(line);
[widthArray release];
[angleArray release];

Now, if you build and run, you should get something similar to the following result. I set a dark background view in IB. I thought it would look more cool!

iNVASIVECODE

I pulled out a screenshot with the glyphs bounding boxes…did you answered correctly to my previous questions? How many glyphs do you find in the text? Here they are:

Glyphs

And another screenshot with the bounding boxes defined by the line metrics.

Metrics

Go ahead and play with all the parameters that we have set along the code. You can get results like the ones I’m showing you below.

Example 1

Example 2


转载自:http://invasivecode.tumblr.com/core-text

<think>我们正在处理用户的问题:如何在Ubuntu上配置OpenCV开发环境并修复VSC中的fatal error: opencv2/core/core.hpp缺失问题。 根据引用,用户遇到了无法找到opencv头文件的问题。我们将参考引用中提供的信息,特别是引用[3]和引用[4]中的环境配置方法。 步骤: 1. 安装OpenCV:在Ubuntu上可以通过包管理器安装,也可以从源码编译安装。 2. 配置VSCode环境,确保编译器能够找到OpenCV的头文件和库。 由于用户使用的是Ubuntu,我们主要使用命令行和VSCode配置。 具体步骤: 第一步:安装OpenCV 方法1:使用apt安装(简单,但可能不是最新版本) sudo apt update sudo apt install libopencv-dev 方法2:从源码编译安装(获取最新版本,但过程较长) 参考官方文档:https://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html 第二步:验证OpenCV安装 安装完成后,我们可以通过以下命令检查OpenCV的版本: pkg-config --modversion opencv4 第三步:配置VSCode 在VSCode中,我们需要配置c_cpp_properties.json(用于IntelliSense)和tasks.json(用于构建)以及launch.json(用于调试)。 1. 创建或修改c_cpp_properties.json 按Ctrl+Shift+P,输入C/C++: Edit Configurations (UI),或者直接修改.vscode/c_cpp_properties.json。 在includePath中添加OpenCV的头文件路径。通常,使用apt安装的头文件路径为: /usr/include/opencv4 如果从源码安装,则可能是/usr/local/include/opencv4 2. 配置tasks.json(用于构建) 我们需要确保编译时链接OpenCV库。在tasks.json中,设置编译命令,添加`pkg-config`来获取正确的编译选项。 示例tasks.json(使用g++): { "version": "2.0.0", "tasks": [ { "type": "shell", "label": "g++ build active file", "command": "/usr/bin/g++", "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}", // 使用pkg-config获取OpenCV的编译选项 "`pkg-config", "--cflags", "--libs", "opencv4`" ], "options": { "cwd": "/usr/bin" }, "group": "build" } ] } 3. 配置launch.json(用于调试) 在launch.json中,设置程序路径等。可参考引用[4]中的配置,但注意修改程序路径。 第四步:测试 写一个简单的OpenCV程序,例如读取一张图片并显示。 如果仍然遇到头文件找不到的问题,请检查: - 头文件路径是否正确添加到c_cpp_properties.json的includePath中 - 在tasks.json中,确保pkg-config能够正确获取到OpenCV的编译选项(可以在终端中运行`pkg-config --cflags --libs opencv4`来测试) 另外,引用[3]中提到将编译好的库文件复制到项目目录下,但在Ubuntu下通常不需要,因为系统已经安装了。如果使用源码编译安装,可能需要设置环境变量。 相关问题: 1. 如何从源码编译安装OpenCV? 2. 在VSCode中如何配置多个库的构建? 3. 如何解决OpenCV程序运行时找不到共享库的问题? 注意:在Ubuntu中,使用apt安装的OpenCV4,头文件位于/usr/include/opencv4/opencv2,所以在包含头文件时,可以写: #include <opencv2/opencv.hpp> 或者 #include <opencv2/core/core.hpp> 但是,如果编译器在/usr/include/opencv4目录下查找,那么直接包含<opencv2/opencv.hpp>是找不到的,因为opencv2目录在opencv4目录下。因此,在c_cpp_properties.json中,我们添加的路径应该是/usr/include/opencv4,这样在代码中就可以直接使用#include <opencv2/opencv.hpp>了。 如果使用pkg-config,它会自动添加-I/usr/include/opencv4,所以编译时不会有问题。但IntelliSense需要知道这个路径,所以c_cpp_properties.json中必须包含。 下面给出一个完整的配置示例: 1. c_cpp_properties.json: { "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "/usr/include/opencv4" // 添加这一行 ], "defines": [], "compilerPath": "/usr/bin/g++", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "gcc-x64" } ], "version": 4 } 2. tasks.json: { "version": "2.0.0", "tasks": [ { "type": "shell", "label": "g++ build active file", "command": "/usr/bin/g++", "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}", "`pkg-config", "--cflags", "--libs", "opencv4`" ], "options": { "cwd": "/usr/bin" }, "group": { "kind": "build", "isDefault": true } } ] } 3. launch.json: { "version": "0.2.0", "configurations": [ { "name": "g++ - Build and debug active file", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "g++ build active file", "miDebuggerPath": "/usr/bin/gdb" } ] } 注意:在tasks.json中,我们使用了反引号来执行pkg-config命令,这在shell中是有效的。但是,在VSCode的tasks.json中,使用反引号可能会被当作普通字符,所以有时需要将整个命令写在一行,并使用shell来解析。上面的写法是可行的,因为"type": "shell"会使用shell来执行命令。 如果上述方法不行,也可以将pkg-config命令拆开,用数组形式传递参数,但注意不要用反引号,而是分别写出参数: "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}", "$(pkg-config --cflags --libs opencv4)" ], 但是,这种写法在tasks.json中是不被支持的。因此,我们使用反引号,并确保使用shell执行。 另一种方法是,将pkg-config命令的结果作为一个字符串,我们可以先通过终端获取: pkg-config --cflags --libs opencv4 然后将输出的字符串拆分成多个参数,手动添加到args中。但这样不灵活,不推荐。 最后,如果用户按照上述步骤配置后仍然报错,请检查OpenCV是否安装正确,以及pkg-config是否能找到opencv4: pkg-config --modversion opencv4 # 查看版本 pkg-config --cflags opencv4 # 查看包含路径 pkg-config --libs opencv4 # 查看链接库 如果pkg-config找不到opencv4,可能需要设置环境变量PKG_CONFIG_PATH,例如: export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig # 如果opencv4.pc在这个目录下 然后重启VSCode,使环境变量生效。 希望这些步骤能帮助用户解决问题。</think>### 在Ubuntu上配置OpenCV开发环境并修复VSCode头文件缺失问题 #### 步骤1:安装OpenCV依赖库 ```bash sudo apt update sudo apt install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt install libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libdc1394-22-dev ``` #### 步骤2:安装OpenCV(二选一) **方法1:APT安装(推荐)** ```bash sudo apt install libopencv-dev ``` **方法2:源码编译安装(获取最新版)** ```bash git clone https://github.com/opencv/opencv.git cd opencv mkdir build && cd build cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. make -j$(nproc) sudo make install ``` #### 步骤3:验证OpenCV安装 ```bash pkg-config --modversion opencv4 # 应显示版本号 find /usr -name "core.hpp" 2>/dev/null # 查找头文件位置 # 正常输出示例:/usr/include/opencv4/opencv2/core/core.hpp ``` #### 步骤4:配置VSCode环境 1. **修改`c_cpp_properties.json`** 添加OpenCV头文件路径(按`Ctrl+Shift+P` → 输入`C/C++: Edit Configurations (UI)`): ```json "includePath": [ "${workspaceFolder}/**", "/usr/include/opencv4" // 关键路径![^3] ] ``` 2. **配置`tasks.json`编译器参数** (按`Ctrl+Shift+P` → 输入`Tasks: Configure Task`): ```json "args": [ "-g", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}", "`pkg-config", "--cflags", "--libs", "opencv4`" // 自动链接库[^4] ] ``` 3. **配置`launch.json`调试环境** (按`Ctrl+Shift+P` → 输入`Debug: Add Configuration`): ```json { "name": "C++ OpenCV Debug", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "preLaunchTask": "C/C++: g++ build active file" // 匹配tasks.json的label } ``` #### 步骤5:测试代码(保存为`test.cpp`) ```cpp #include <opencv2/core/core.hpp> #include <iostream> int main() { std::cout << "OpenCV version: " << CV_VERSION << std::endl; return 0; } ``` 编译运行:`Ctrl+Shift+B` → 选择编译器任务 → `F5`调试 #### 常见问题修复 1. **头文件缺失错误** - 确认`/usr/include/opencv4`存在 - 在`c_cpp_properties.json`中添加路径:`"/usr/local/include/opencv4"`(源码安装时)[^3] 2. **链接库错误** 在`tasks.json`中添加: ```json "args": [..., "-L/usr/local/lib", "-lopencv_core", "-lopencv_highgui"] ``` 3. **环境变量更新** ```bash sudo ldconfig # 刷新动态链接库缓存 ``` > 关键原理:VSCode需明确知道头文件位置(`includePath`)和链接库参数(`pkg-config`)[^4]。通过APT安装的标准路径为`/usr/include/opencv4`,源码安装则需指定`/usr/local/include/opencv4`[^3]。 --- ### 相关问题 1. **如何在VSCode中配置多个OpenCV版本?** [需管理不同版本的`includePath`和链接参数] 2. **编译OpenCV时出现`ippicv`下载失败如何解决?** [需手动下载依赖并配置CMake缓存] 3. **如何将OpenCV项目部署到没有安装OpenCV的机器?** [需静态链接库和依赖项打包] 4. **在ARM架构的Ubuntu上编译OpenCV有哪些注意事项?** [需调整NEON优化和硬件加速配置] > 引用说明: > [^3]: 环境配置需确保头文件路径和编译模式匹配(Release/Debug) > [^4]: VSCode的`tasks.json`需正确传递`pkg-config`参数实现自动链接
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值