Merge pull request #28704 from komakai:replace-jazzy-with-docc

Use Xcode DocC tool for buiding Apple platform docs + add quick-start tutorial #28704

### Pull Request Readiness Checklist

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work

This PR resolves https://github.com/opencv/opencv/issues/28702 and https://github.com/opencv/opencv/issues/28703
Additionally it resolves build issues on Apple platforms by making build scripts run with Python 3
This commit is contained in:
Giles Payne
2026-05-14 17:22:04 +09:00
committed by GitHub
parent eadaab5fbb
commit 7cc6d8576c
36 changed files with 359 additions and 176 deletions

View File

@@ -0,0 +1,69 @@
This is the documentation for the iOS OpenCV framework.
## Quick Start Tutorial
Follow the steps below to get a simple "Hello World" app running on iOS
* Open Xcode and create a new project from the **File** > **New** > **Project...** menu
![New Project menu](common-create-project.png)
* From template selection dialog select the `iOS` tab and then select `App`
![Template selection dialog](ios-create-app.png)
* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift**
![App settings dialog](ios-create-app-settings.png)
* Choose a location to save the project
![Choose project location dialog](common-create-app-location.png)
* Create a new folder `Framework`
![Create new folder menu](ios-create-framework-folder.png)
* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`)
![Add framework](ios-add-framework.png)
* In the project navigator select the `ViewController.swift` file
![Select ViewController file](ios-select-viewcontroller.png)
* Add an `import` for the OpenCV framework below the existing import for `UIKit`
```swift, copy
import opencv2
```
* Replace the implementation of the `viewDidLoad` function with the following code
```swift, copy
override func viewDidLoad() {
super.viewDidLoad()
let white = Scalar(255, 255, 255, 255)
let black = Scalar(0, 0, 0, 255)
let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white)
Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 150), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black)
let image = m.toUIImage()
let imageView = UIImageView(image: image)
self.view.addSubview(imageView)
}
```
* The `ViewController.swift` file should now look like this
![Updated ViewController code](ios-add-opencv-code.png)
* Launch the app
![Launch app button](ios-launch-app.png)
* The app home screen should look like this
![App screenshot](ios-app-screenshot.png)
If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements.

View File

@@ -0,0 +1,85 @@
This is the documentation for the macOS OpenCV framework.
## Quick Start Tutorial
Follow the steps below to get a simple "Hello World" app running on macOS
* Open Xcode and create a new project from the **File** > **New** > **Project...** menu
![New Project menu](common-create-project.png)
* From the template selection dialog select the **macOS** tab and then select **App**
![Template selection dialog](macos-create-app.png)
* Enter a name for the project and set **Interface** to **Storyboard** and **Language** to **Swift**
![App settings dialog](macos-create-app-settings.png)
* Choose a location to save the project
![Choose project location dialog](common-create-app-location.png)
* Create a new folder `Framework`
![Create new folder menu](macos-create-framework-folder.png)
* In a Finder window copy the framework file to the newly created folder. The framework should appear in the project navigator as below. (Note: in this case the framework has extension `.framework` but depending on how you built it or from where you obtained it your framework may have extension `.xcframework`)
![Add framework](macos-add-framework.png)
* Select the project root in the project navigator and on the **General** tab locate the **Frameworks, Libraries and Embedded Content** section
![Frameworks, Libraries and Embedded Content](macos-add-dependencies-1.png)
* In the **Choose frameworks and libraries to add** dialog add the following
* OpenCL.framework
* liblapack.tbd
* libblas.tbd
![Choose frameworks and libraries to add](macos-add-dependencies-2.png)
* The **Frameworks, Libraries and Embedded Content** section should now look like this
![Choose frameworks and libraries to add](macos-add-dependencies-3.png)
* In the project navigator select the `ViewController.swift` file
![Select ViewController file](macos-select-viewcontroller.png)
* Add an `import` for the OpenCV framework below the existing import for `Cocoa`
```swift, copy
import opencv2
```
* Replace the implementation of the `viewDidLoad` function with the following code
```swift, copy
override func viewDidLoad() {
super.viewDidLoad()
let white = Scalar(255, 255, 255, 255)
let black = Scalar(0, 0, 0, 255)
let m = Mat(rows: 400, cols: Int32(view.frame.width), type: CvType.CV_8UC4, scalar: white)
Imgproc.putText(img: m, text: "Hello World", org: Point(x: 10, y: 250), fontFace: HersheyFonts.FONT_HERSHEY_DUPLEX, fontScale: 2, color: black)
let image = m.toNSImage()
let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: Int(m.cols()), height: Int(m.rows())))
imageView.image = image
self.view.addSubview(imageView)
}
```
* The `ViewController.swift` file should now look like this
![Updated ViewController code](macos-add-opencv-code.png)
* Launch the app
![Launch app button](macos-launch-app.png)
* The app home screen should look like this
![App screenshot](macos-app-screenshot.png)
If that has all worked well then congratulations. If not then feel free to reach out to the OpenCV community for help. Also feel free to contribute fixes and improvements.

View File

@@ -1,13 +0,0 @@
## About
This is the documentation for the Objective-C/Swift OpenCV wrapper
To get started: add the OpenCV framework to your project and add the following to your source
###Objective-C
#import <OpenCV/OpenCV.h>
###Swift
import OpenCV

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
from __future__ import print_function, unicode_literals
import sys, re, os.path, errno, fnmatch
@@ -100,6 +100,9 @@ method_dict = {
("Mat", "dot") : "-dot:"
}
enum_value_lookup = {}
enums = set()
modules = []
@@ -708,12 +711,25 @@ def see_lookup(objc_class, see):
if (see_class, see_method) in method_dict:
method = method_dict[(see_class, see_method)]
if see_class == objc_class:
return method
return "``{}``".format(method[1:])
else:
return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]"
return "``{}/{}``".format(see_class, method[1:])
else:
return see
def hash_link(link_text, objc_class, function_name):
if link_text == function_name:
return "*{}*".format(link_text) # make bold
elif link_text in enums:
return "``{}``".format(link_text)
elif link_text in enum_value_lookup:
return "``{}/{}``".format(enum_value_lookup[link_text], link_text)
elif (objc_class, link_text) in method_dict:
return "``{}/{}``".format(objc_class, link_text)
elif ("Core", link_text) in method_dict:
return "``Core/{}``".format(link_text)
else:
return "#{}".format(link_text) # leave as is
class ObjectiveCWrapperGenerator(object):
def __init__(self):
@@ -1115,13 +1131,16 @@ class ObjectiveCWrapperGenerator(object):
if fi.docstring:
lines = fi.docstring.splitlines()
toWrite = []
last_line_was_param = False
for index, line in enumerate(lines):
line = re.sub(r"([(\s])#(.+?)([).,\s]|$)", lambda x: x.group(1) + hash_link(x.group(2), ci.objc_name, fi.name) + x.group(3), line)
p0 = line.find("@param")
if p0 != -1:
p0 += 7 # len("@param" + 1)
p1 = line.find(' ', p0)
p1 = len(line) if p1 == -1 else p1
name = line[p0:p1]
last_line_was_param = True
for arg in args:
if arg.name == name:
toWrite.append(re.sub(r'\*\s*@param ', '* @param ', line))
@@ -1129,10 +1148,14 @@ class ObjectiveCWrapperGenerator(object):
else:
s0 = line.find("@see")
if s0 != -1:
if last_line_was_param:
# separate from @param lines
toWrite.append(" *")
sees = line[(s0 + 5):].split(",")
toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees]))
toWrite.append(line[:s0] + "- SeeAlso " + ", ".join([see_lookup(ci.objc_name, see.strip()) for see in sees]))
else:
toWrite.append(line)
last_line_was_param = False
for line in toWrite:
method_declarations.write(line + "\n")
@@ -1317,6 +1340,10 @@ $unrefined_call$epilogue$ret
if ci.cname in enum_fix:
typeNameShort = enum_fix[ci.cname].get(typeNameShort, typeNameShort)
enums.add(typeNameShort)
for c in consts:
enum_value_lookup[c.name] = typeNameShort
ci.enum_declarations.write("""
// C++: enum {1} ({2})
typedef NS_ENUM(int, {1}) {{
@@ -1462,16 +1489,37 @@ typedef NS_ENUM(int, {1}) {{
self.save(opencv_modulemap_file, opencv_modulemap)
available_modules = " ".join(["-DAVAILABLE_" + m['name'].upper() for m in config['modules']])
cmakelist_template = read_contents(os.path.join(SCRIPT_DIR, 'templates/cmakelists.template'))
cmakelist = Template(cmakelist_template).substitute(modules = ";".join(modules), framework = framework_name, objc_target=objc_target, module_availability_defines=available_modules)
doc_catalog = output_objc_path + "/../Documentation.docc"
doc_resources = os.path.join(doc_catalog, "Resources")
cmakelist = Template(cmakelist_template).substitute(
modules = ";".join(modules),
framework = framework_name,
objc_target = objc_target,
module_availability_defines = available_modules,
)
self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist)
mkdir_p(doc_resources)
mkdir_p(os.path.join(output_objc_build_path, "framework_build"))
mkdir_p(os.path.join(output_objc_build_path, "test_build"))
mkdir_p(os.path.join(output_objc_build_path, "doc_build"))
with open(os.path.join(SCRIPT_DIR, '../doc/README.md')) as readme_in:
readme_body = readme_in.read()
readme_body += "\n\n\n##Modules\n\n" + ", ".join(["`" + m.capitalize() + "`" for m in modules])
with open(os.path.join(output_objc_build_path, "doc_build/README.md"), "w") as readme_out:
readme_out.write(readme_body)
landing_page_body_in = ""
if objc_target == "ios" or objc_target == "osx":
with open(os.path.join(SCRIPT_DIR, '../doc/LandingPage_' + objc_target + '.md')) as landing_page_in:
landing_page_body_in = landing_page_in.read()
landing_page_body_out = "# ``" + framework_name + "`` Framework\n\n" + landing_page_body_in
landing_page_body_out += "\n\n\n## Modules\n\n" + ", ".join(["``" + m.capitalize() + "``" for m in modules]) + "\n"
with open(os.path.join(doc_catalog, "LandingPage.md"), "w") as landing_page_out:
landing_page_out.write(landing_page_body_out)
for m in config['modules']:
pic_files = os.path.join(ROOT_DIR, m['location'], 'doc', 'pics')
if os.path.exists(pic_files):
copy_tree(pic_files, doc_resources)
if objc_target == "ios":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/ios'), doc_resources)
if objc_target == "osx":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/osx'), doc_resources)
if objc_target == "ios" or objc_target == "osx":
copy_tree(os.path.join(SCRIPT_DIR, '../doc/pics/common'), doc_resources)
if framework_name != "OpenCV":
for dirname, dirs, files in os.walk(os.path.join(testdir, "test")):
if dirname.endswith('/resources'):
@@ -1541,10 +1589,11 @@ def sanitize_documentation_string(doc, type):
doc = re.sub(re.compile('\\\\f\\$(.*?)\\\\f\\$', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\[(.*?)\\\\f\\]', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(re.compile('\\\\f\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
doc = re.sub(r"!\[(.*)]\((?:.*/)*(.+)\.(?:png|jpg|svg)\)", "![\\1](\\2)", doc)
doc = doc.replace("@anchor", "") \
.replace("@brief ", "").replace("\\brief ", "") \
.replace("@cite", "CITE:") \
.replace("@cite", "*Cite:*") \
.replace("@code{.cpp}", "<code>") \
.replace("@code{.txt}", "<code>") \
.replace("@code", "<code>") \
@@ -1556,14 +1605,14 @@ def sanitize_documentation_string(doc, type):
.replace("@endcode", "</code>") \
.replace("@endinternal", "") \
.replace("@file", "") \
.replace("@include", "INCLUDE:") \
.replace("@include", "Include:") \
.replace("@ingroup", "") \
.replace("@internal", "") \
.replace("@overload", "") \
.replace("@param[in]", "@param") \
.replace("@param[out]", "@param") \
.replace("@ref", "REF:") \
.replace("@note", "NOTE:") \
.replace("@ref", "*Ref:*") \
.replace("@note", "*Note:*") \
.replace("@returns", "@return") \
.replace("@sa ", "@see ") \
.replace("@snippet", "SNIPPET:") \

View File

@@ -22,7 +22,14 @@ else()
endif()
file(GLOB_RECURSE objc_headers "*\.h")
add_library($framework STATIC $${objc_sources})
set_source_files_properties(Documentation.docc PROPERTIES
XCODE_LAST_KNOWN_FILE_TYPE folder.documentationcatalog
)
add_library($framework STATIC
$${objc_sources}
Documentation.docc
)
set_target_properties($framework PROPERTIES LINKER_LANGUAGE CXX)

View File

@@ -0,0 +1,11 @@
# $framework Documentation - How To View
## View Locally
* Start a web server in the directory where this HowTo file is located. For example ``python3 -m http.server 8000``
* In a browser navigate to URL http://127.0.0.1:8000/$hosting_base_path/documentation/$framework/
## Deploying to a server
* Upload the content of the directory where this HowTo file is located to the document root of the web server maintaining the same folder hierarchy
* In a browser navigate to URL https://{server-domain}/$hosting_base_path/documentation/$framework/