From a0188eea7506dfe7773180084020bd38d25f2353 Mon Sep 17 00:00:00 2001
From: LeonMatthesKDAB Reading t
❓ How can you change the connection type when connecting to a signal from Rust?
✅ Check that you can successfully print "Hello world from CXX-Qt!" by selecting a file path or changing the image filter
-Before we implement the image loading and filtering, we'll need some additional types:
-Before we implement the image loading and filtering, we'll need to import QSizeF and QRectF to calculate the size for painting.
✅ Import these types from cxx-qt-lib
✅ Add QSizeF and QRectF to the #[cxx_qt::bridge]
We'll also need a few more functions from C++ to paint the image, get the size of the ImagePainter, etc.
@@ -1175,18 +1171,13 @@Documentation that may be useful:
image
crate to learn more about how to use the resulting image.✅ Add threading in Rust via std::thread::spawn
to load and convert the image in the background
✅ Add a BusyIndicator
to QML to show that the background thread is wthat the background thread is waiting.
✅ Add a BusyIndicator
to QML to show that the background thread is waiting.
The resulting application should look like this:
✅ Check out the full example code and compare it with your implementation
diff --git a/searchindex.js b/searchindex.js index 5c60716..671b739 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Object.assign(window.search, {"doc_urls":["index.html#rust--qt-workshop","index.html#part-1--setup","index.html#part-2--tutorial","preparations.html#preparations","preparations.html#workshop-materials","preparations.html#required-software","setup.html#setup","setup.html#the-rust-toolchain","setup.html#additional-tooling","setup.html#linux","setup.html#windows","setup.html#macos","tooling-check.html#tooling-check","tooling-check.html#setup-check","tooling-check.html#qt-specifics","tooling-check.html#hello-world-with-qt","tutorial/index.html#idea","tutorial/cli.html#command-line-interface","tutorial/cli/hello-world.html#hello-world-on-the-command-line","tutorial/cli/image-filters.html#image-filter-application","tutorial/cli/final-code.html#final-application","tutorial/library.html#library","tutorial/library/recreate-as-workspace.html#recreate-the-project-as-workspace","tutorial/library/create-library.html#creating-a-library","tutorial/library/create-library.html#reading-the-image","tutorial/library/cli-with-library.html#creating-a-cli-using-the-library","tutorial/qt-gui/index.html#building-a-qt-qml-gui-with-rust","tutorial/qt-gui/build-setup.html#building-a-simple-qt-application-with-rust","tutorial/qt-gui/creating-the-gui.html#creating-the-qml-gui","tutorial/qt-gui/qquickpainteditem.html#creating-a-qquickpainteditem-in-rust","tutorial/qt-gui/rust-implementation.html#adding-behavior-in-rust","references.html#references"],"index":{"documentStore":{"docInfo":{"0":{"body":21,"breadcrumbs":5,"title":3},"1":{"body":6,"breadcrumbs":5,"title":3},"10":{"body":90,"breadcrumbs":3,"title":1},"11":{"body":40,"breadcrumbs":3,"title":1},"12":{"body":0,"breadcrumbs":5,"title":2},"13":{"body":70,"breadcrumbs":5,"title":2},"14":{"body":65,"breadcrumbs":5,"title":2},"15":{"body":55,"breadcrumbs":6,"title":3},"16":{"body":95,"breadcrumbs":2,"title":1},"17":{"body":41,"breadcrumbs":4,"title":3},"18":{"body":26,"breadcrumbs":7,"title":4},"19":{"body":336,"breadcrumbs":8,"title":3},"2":{"body":19,"breadcrumbs":5,"title":3},"20":{"body":84,"breadcrumbs":5,"title":2},"21":{"body":40,"breadcrumbs":3,"title":1},"22":{"body":90,"breadcrumbs":8,"title":3},"23":{"body":101,"breadcrumbs":6,"title":2},"24":{"body":166,"breadcrumbs":6,"title":2},"25":{"body":97,"breadcrumbs":10,"title":4},"26":{"body":40,"breadcrumbs":7,"title":5},"27":{"body":393,"breadcrumbs":11,"title":5},"28":{"body":104,"breadcrumbs":8,"title":3},"29":{"body":668,"breadcrumbs":7,"title":3},"3":{"body":7,"breadcrumbs":2,"title":1},"30":{"body":447,"breadcrumbs":8,"title":3},"31":{"body":27,"breadcrumbs":2,"title":1},"4":{"body":20,"breadcrumbs":3,"title":2},"5":{"body":9,"breadcrumbs":3,"title":2},"6":{"body":13,"breadcrumbs":3,"title":1},"7":{"body":52,"breadcrumbs":4,"title":2},"8":{"body":82,"breadcrumbs":4,"title":2},"9":{"body":44,"breadcrumbs":3,"title":1}},"docs":{"0":{"body":"This workbook contains the material for a Rust & Qt workshop held by Ferrous Systems , developed and held together with KDAB . It is based on work by Jan-Erik Rediger . It is split into 2 parts:","breadcrumbs":"Rust and Qt » Rust & Qt workshop","id":"0","title":"Rust & Qt workshop"},"1":{"body":"An installation guide for all tooling used throughout this book.","breadcrumbs":"Rust and Qt » Part 1: Setup","id":"1","title":"Part 1: Setup"},"10":{"body":"We have had the best experience with installing Qt on Windows using the Qt online installer . Make sure to select and install a version of Qt 6. Then add the installation directory to your PATH environment variable and make sure qmake is in your PATH by running qmake --version. You may have to restart your terminal for this to work correctly. Note that on Windows the Qt installation usually shouldn't be in your system path. That may cause issues with other programs that link to Qt dynamically. CXX-Qt still needs to be able to find the qmake executable however. A good compromise is to create a development environment which either temporarily adds the Qt installation in the PATH or assigns the QMAKE environment variable to point to the correct qmake executable. If this is too much trouble to set up you can always fall back to providing the QMAKE environment variable each time you execute cargo build or cargo run.","breadcrumbs":"Preparations » Setup » Windows","id":"10","title":"Windows"},"11":{"body":"Preferrably install Qt using the Qt online installer . Make sure to select and install a version of Qt 6. You can also try installing Qt using homebrew. $ brew install qt6 In any case, make sure the installation directory is added to your path and qmake can be found by your command line using qmake --version. You may have to restart your terminal for this to work correctly.","breadcrumbs":"Preparations » Setup » MacOS","id":"11","title":"MacOS"},"12":{"body":"","breadcrumbs":"Preparations » Tooling check » Tooling check","id":"12","title":"Tooling check"},"13":{"body":"✅ Fully restart your terminal (not just open a fresh tab). ✅ Let's check that you have installed Rust. $ rustc --version\nrustc 1.73.0 (cc66ad468 2023-10-03) $ cargo --version\ncargo 1.73.0 (9c4383fb5 2023-08-26) ✅ In a work directory, run: $ cargo new --bin hello-world\n$ cd hello-world\n$ cargo run --release Compiling hello-world v0.1.0 (C:\\Code\\ferrous\\hello-world) Finished release [optimized] target(s) in 0.99s Running `target\\release\\hello-world.exe`\nHello, world! This ensures that the whole toolchain works correctly and finds the system linker. This should work at all times, if it doesn't, immediately ask for a trainer.","breadcrumbs":"Preparations » Tooling check » Setup check","id":"13","title":"Setup check"},"14":{"body":"For those interested in following the Qt section of the course, the Rust toolchain must be able to find the Qt installation. The Rust-Qt bindings ( CXX-Qt ) need to be able to find your Qt installation. For this it relies on the qmake executable. CXX-Qt will try to find qmake in your path. ✅ Confirm that qmake reports a version of Qt 6 $ qmake --version\nQMake version 3.1\nUsing Qt version 6.5.1 in /usr/lib64 If for some reason you do not want to add qmake to your path, you can use the QMAKE environment variable to tell CXX-Qt where to find the Qt6 qmake executable. e.g.: QMAKE=/usr/bin/qmake6 cargo run","breadcrumbs":"Preparations » Tooling check » Qt specifics","id":"14","title":"Qt specifics"},"15":{"body":"To test that the Rust toolchain can indeed find and link to your Qt installation, you can clone the training repository and run the qt-hello-world example crate. ✅ Clone the repository $ git clone https://github.com/ferrous-systems/qt-training-2023 ✅ Navigate to the qt-hello-world crate $ cd qt-training-2023/crates/qt-hello-world/ ✅ Test that it works $ cargo run If you don't have qmake in your PATH, use: $ QMAKE=/path/to/your/qmake cargo run The resulting application should look like this: A Qt Application saying \"Hello World\"","breadcrumbs":"Preparations » Tooling check » Hello world with Qt","id":"15","title":"Hello world with Qt"},"16":{"body":"We now want to build a slightly more complex application. The idea is to use an existing image manipulation library to apply filters to a given image [1] . This example will show us how to use an existing Rust crate, how to handle input and output and how to interact with the different environments. We start off with building a command-line tool run using wasmtime, then build a web application running completely client-side, and last as an edge computing API that processes images posted to it. We will work with the following example image (but really any image will work). Right-click it and save it to disk for later use. When applying the filter named \"1977\", this is the result: Several more filters are available in the library. The image filters are inspired by Instagram. The implementation is based on CSSgram , which was ported to Rust by @ha-shine . The example image was taken on 2022-10-28 by Jan-Erik Rediger.","breadcrumbs":"Idea » Idea","id":"16","title":"Idea"},"17":{"body":"In this tutorial you'll get familiar with: Building Rust code for your local target Running applications on the command-line Parsing command line parameters by hand Re-using existing crates in your application Rust type systems basics We start with a command-line tool that takes an image and a filter name as input. It applies the given filter to the image and produces an output.png.","breadcrumbs":"CLI » Command-line interface","id":"17","title":"Command-line interface"},"18":{"body":"✅ Create a new Rust project cargo new rustagram\ncd rustagram ✅ To start the tool will only print a message. Open src/main.rs and add fn main() { println!(\"Hello World from wasmtime!\");\n} Next, start integrating image filters .","breadcrumbs":"CLI » Hello World » Hello World on the command line","id":"18","title":"Hello World on the command line"},"19":{"body":"Now that you can build and run an application compiled to WebAssembly, it's time to build some functionality into it. The goal is: Take an input file, a filter name and, optionally, an output file (or \"output.jpg\" as the default). Load the input file, apply the given filter to this image, then write the resulting image to the output file. You can continue with the previously created project. ✅ Open src/main.rs again and replace the println! line with code to parse the arguments. fn main() { let mut args = std::env::args().skip(1); let input = args.next().expect(\"INPUT required\"); let filter = args.next().expect(\"FILTER required\"); let output = args.next().unwrap_or_else(|| \"output.jpg\".to_string()); dbg!((input, filter, output));\n} ✅ Build and run this to make sure it works as expected. ✅ Now add a dependency to handle image manipulation. The image filters are readily available in the rustagram2 crate. Add the rustagram2 crate as a dependency in rustagram/Cargo.toml [dependencies]\nrustagram2 = \"2.0.0\" The documentation is available on docs.rs/rustagram2 . ✅ You need a FilterType to apply later. rustagram2 shows the available filters in the FilterType documentation . It also has FromStr from the standard library implemented for it, so you can parse strings into the filter type by calling parse() on the string. let filter_type = filter.parse().expect(\"can't parse filter name\"); An unknown filter name would cause an error. For now you don't need to handle that. Your application can just panic and exit. If you compile everything at this point you will probably hit a type annotation error. You can try to resolve that now. You can also continue and observe how this error will be resolved once you add more code in the next steps. Now comes the main part of the application: load the image, apply the filter and save the resulting file. This is a small challenge for you to write, but the next steps guide you through it. ✅ You need to read the file from disk and turn it into an object you can work with. image::open does that for you easily. Don't worry about error handling and just unwrap. ✅ The image type you get is able to represent a wide variety of image types. For this tutorial you want an RgbaImage . You can convert your image using the to_rgba8 method. ✅ Last but not least you need to apply the selected filter on this image. The rustagram2 crate implements that as the apply_filter method on a trait. This trait is automatically implemented for the RgbaImage type you got from to_rgba8. ✅ Save back to the file output by using the save method available on the image. With the help of the documentation this should be achievable in a couple of lines of code. Try it for yourself! ✅ Once you wrote the code, build it again and try to run it. Expected output when you don't pass any arguments: $ cargo run\nthread 'main' panicked at src\\main.rs:5:29:\nINPUT required\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Expected output when you pass a non-existing file path and a filter name: $ cargo run -- missing-image.jpg 1977 Finished dev [unoptimized + debuginfo] target(s) in 0.09s Running `target\\debug\\rustagram.exe kongelige-slott.jpg 1977`\nthread 'main' panicked at src\\main.rs:10:34:\ncalled `Result::unwrap()` on an `Err` value: IoError(Os { code: 2, kind: NotFound, message: \"The system cannot find the file specified.\" })\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Expected output when you pass the file actually exists. $ cargo run -- kongelige-slott.jpg 1977 Finished dev [unoptimized + debuginfo] target(s) in 0.09s Running `target\\debug\\rustagram.exe kongelige-slott.jpg 1977` The result should then be stored in output.jpg.","breadcrumbs":"CLI » Image filter CLI application » Image filter application","id":"19","title":"Image filter application"},"2":{"body":"A hands-on tutorial writing Rust and compiling it in 3 variations: as a command-line app, as a library and as a small Qt app using the library using CXX-Qt.","breadcrumbs":"Rust and Qt » Part 2: Tutorial","id":"2","title":"Part 2: Tutorial"},"20":{"body":"You should have this file tree layout: $ tree\n.\n├── Cargo.lock\n├── Cargo.toml\n└── src └── main.rs To recap your final code should look something like this: use rustagram::{image, RustagramFilter}; fn main() { let mut args = std::env::args().skip(1); let input = args.next().expect(\"INPUT required\"); let filter = args.next().expect(\"FILTER required\"); let output = args.next().unwrap_or_else(|| \"output.jpg\".to_string()); let filter_type = filter.parse().expect(\"can't parse filter name\"); let img = image::open(input).unwrap(); let out = img.to_rgba8().apply_filter(filter_type); out.save(output).unwrap();\n} You can build your code like this: cargo build And run it using cargo: cargo run For an optimized build use: cargo build --release cargo run --release Some ideas on what to do next: Run the application natively: cargo run. Any complications or differences? Heard of WebAssembly? You can actually run this in WebAssembly - see our WASM Training Try a full command line parser crate - see blessed.rs for suggestions","breadcrumbs":"CLI » Final application » Final application","id":"20","title":"Final application"},"21":{"body":"In this tutorial you'll get familiar with: Refactoring your Rust project into multiple libraries (crates) Running applications on the command-line Parsing command line paramenters by hand Re-using existing crates in your application Learn about borrowing We start with a command-line tool that takes an image and a filter name as input. It applies the given filter to the image and produces an output.png.","breadcrumbs":"Implementing a library » Library","id":"21","title":"Library"},"22":{"body":"Let's get started by creating a so-called \"cargo workspace\". In a growing project, this is a common refactoring, however, given the simplicity of our CLI app, it's easier to just start from the beginning again. ✅ Create yourself an working directory $ mkdir image-workspace\n$ cd image-workspace ✅ create a library crate for image manipulation $ cargo new --lib image-manipulation ✅ create a binary crate for the CLI app $ cargo new --bin cli You folder structure should currently look like this: $ tree\n.\n└── cli\n└── image-manipulation ✅ create a file called Cargo.toml in the main folder: $ tree\n.\n└── Cargo.toml\n└── cli\n└── image-manipulation ✅ fill that file with the following info: [workspace]\nmembers = [\"cli\", \"image-manipulation\"]\nresolver = \"2\" ✅ build the whole workspace once to check everything works $ cargo build Move on to creating the library .","breadcrumbs":"Implementing a library » Recreate the project as a workspace » Recreate the project as workspace","id":"22","title":"Recreate the project as workspace"},"23":{"body":"In this section of the tutorial, you will create a library to handle image manipulation. This library will have exactly one function. It should: Take a field of bytes representing an image Take a filter type It should not: Handle any I/O Do input parsing (like filter name detection, etc.) ✅ In image-manipulation/Cargo.toml, add: [dependencies]\nrustagram2 = \"2\"\nlog = \"0.4\" We use the log crate to get some visibility into what is happening. ✅ In image-manipulation/src/lib.rs, add the following imports and function headers use std::io::Cursor; use rustagram::image::io::Reader;\nuse rustagram::image::ImageOutputFormat;\nuse rustagram::{RustagramFilter, FilterType}; pub fn apply_filter(img: &[u8], filter: FilterType) -> Vec✅ Check that you can successfully print "Hello world from CXX-Qt!" by selecting a file path or changing the image filter
-Before we implement the image loading and filtering, we'll need some additional types:
-Before we implement the image loading and filtering, we'll need to import QSizeF and QRectF to calculate the size for painting.
✅ Import these types from cxx-qt-lib
✅ Add QSizeF and QRectF to the #[cxx_qt::bridge]
We'll also need a few more functions from C++ to paint the image, get the size of the ImagePainter, etc.
@@ -325,18 +321,13 @@image
crate to learn more about how to use the resulting image.✅ Add threading in Rust via std::thread::spawn
to load and convert the image in the background
✅ Add a BusyIndicator
to QML to show that the background thread is wthat the background thread is waiting.
✅ Add a BusyIndicator
to QML to show that the background thread is waiting.
The resulting application should look like this:
✅ Check out the full example code and compare it with your implementation