# fuqr
feeling unemployed qr code generator
## Usage
```rs
let qr_code = generate("https://github.com/zhengkyl/fuqr", &QrOptions::new()).unwrap();
```
This is what `QrOptions::new()` looks like.
```rs
QrOptions {
min_version: Version(1),
strict_version: false,
min_ecl: ECL::Low,
strict_ecl: false,
mode: None, // None = automatically determined
mask: None, // None = automatically determined
}
```
`generate()` has two possible errors.
`QrError::InvalidEncoding` occurs if `Mode::Numeric` or `Mode::Alphanumeric` is specified and the input string contains invalid characters. `None` or `Mode::Byte` will not error.
`QrError::ExceedsMaxCapacity` is what it sounds like, but unless `strict_version` is set to true, this is very hard to trigger. The lower limit is exceeding 1273 characters with `Mode::Byte` and `ECL::High`. See [capacity table](https://www.thonky.com/qr-code-tutorial/character-capacities) for specifics.
### QArt Codes
Based on Russ Cox's [QArt codes](https://research.swtch.com/qart) with one improvement. The decoded message doesn't contain extra garbage data, because only the padding bits are manipulated.
I first saw this improvement from https://github.com/xyzzy/qrpicture (actual code not available).
```rs
let version = Version::new(13);
let qr_width = version.0 * 4 + 17;
let pixel_weights = vec![WeightPixel::new(false, 0); qr_width * qr_width];
let qr_code = generate_qart(
"https://github.com/zhengkyl/fuqr",
&QrOptions::new().min_version(version),
&pixel_weights
).unwrap();
```
`generate_qart()` has the same errors as `generate()` along with `QartError::InvalidPixelWeights` if the size of `pixel_weights` doesn't match the size of the QR code matrix.
### Advanced Usage
```rs
// This returns None if input string exceeds max capacity
let data = Data::new(
"https://github.com/zhengkyl/fuqr",
Mode::Byte,
Version(1), // minimum Version
ECL::Low, // minimum ECL
).unwrap();
// OR
let data = Data::new_verbose(
"https://github.com/zhengkyl/fuqr",
Mode::Byte,
Version(1),
true, // strict Version
ECL::Low,
true, // strict ECL
).unwrap();
// Pass None to determine and use "best" mask
let qr_code = QrCode::new(data, Some(Mask::M1));
```
The encoding `Mode` must be specified and no errors are thrown if it's invalid. This is fine because it's probably always `Mode::Byte`.
The `strict` arguments force `Version` and `ECL` to not upgrade. There is no real usecase for this.
```rs
// data from above
let mask = Mask::M0;
let bit_info = BitInfo::new(data.mode, data.version, data.ecl, mask);
```
`BitInfo` is like `QrCode`, but it stores the role of each bit/pixel for the specified `Mode`, `Version`, `ECL`, `Mask` combination. Specifically for data pixels, it tracks whether it is a data, error correction, or remainder bit, as well as its error correction block and index within said block.
### NOTE
- MASK SCORING IS (probably) NOT IMPLEMENTED CORRECTLY
- I haven't bothered fixing the code, because it's annoying and probably pointless.
## Examples
All example code is WIP and in a very unpolished state.
### `/examples/bad_apple.rs`
Animated QArt codes.
| [Naive (low scannability)](https://youtu.be/1ems029Rln4) | [Patterns + Low FPS](https://youtu.be/8HG8HJ7tbO8) |
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| [](https://youtu.be/1ems029Rln4) | [](https://youtu.be/8HG8HJ7tbO8) |
```sh
# place video at ./examples/bad_apple/bad_apple.mp4
# i'm using the 3:40 one
# generates frames in ./examples/bad_apple/frames
cargo run --example bad_apple
# combines frames into mp4
ffmpeg -framerate 5 -pattern_type glob -i 'examples/bad_apple/frames/*.png' -c:v libx264 -pix_fmt yuv420p -vf "scale=iw*10:ih*10:flags=neighbor" frames.mp4
# combines frames with audio
ffmpeg -i frames.mp4 -i examples/bad_apple/bad_apple.mp4 -map 0:v:0 -map 1:a:0 -c:v copy -c:a aac -shortest out.mp4
```
### `/examples/scale.rs`
Scaling modules based on position.
| Circle | Stripes | Waves |
| -------------------------------------- | ---------------------------------------- | ------------------------------------ |
| ![circle](./examples/scale_circle.png) | ![stripes](./examples/scale_stripes.gif) | ![waves](./examples/scale_waves.gif) |
### `/examples/weave.rs`
No need to stick to a boring pixel grid.
| Thick | Thin | Diagonal |
| ------------------------------------ | ---------------------------------- | -------------------------------------- |
| ![thick](./examples/weave_thick.png) | ![thin](./examples/weave_thin.png) | ![diagonal](./examples/weave_diag.png) |
### `/examples/layers.rs`
Layering is neat, but it can seriously degrade scanning ability if done without care.
See [Halftone QR Codes](https://cgv.cs.nthu.edu.tw/projects/Recreational_Graphics/Halftone_QRCodes), [Micrography QR Codes](https://cgv.cs.nthu.edu.tw/projects/Recreational_Graphics/MQRC), [Amazing QR](https://github.com/x-hw/amazing-qr) for more thoughtful implementations with high scannability.
| Background | Minimalist | Improved scannability |
| ----------------------------------------------- | ---------------------------------- | -------------------------------------- |
| ![background](./examples/layers_background.png) | ![thin](./examples/layers_min.gif) | ![diagonal](./examples/layers_max.gif) |
### Misc bugs and experiments
| Have | Some | More |
| ------------------------------------------- | ----------------------------------------- | ------------------------------- |
| ![bathroom](./examples/misc/bathroom.png) | ![diamonds](./examples/misc/diamonds.gif) | ![mmm](./examples/misc/mmm.png) |
| ![mountains](./examples/misc/mountains.png) | ![diamonds](./examples/misc/zebra.gif) | |
## Other
- Great QR code generator tutorial
- https://www.thonky.com/qr-code-tutorial/
- Reference generator implementations
- https://github.com/erwanvivien/fast_qr
- https://github.com/unjs/uqr
- Reference scanner implementations
- https://github.com/zxing/zxing
- https://github.com/opencv/opencv_contrib/tree/4.x/modules/wechat_qrcode (fork of zxing-cpp)
### Benchmarks
My benchmarks seem to vary ~30% from run to run. My takeaway is that `fuqr` is kinda slow, but this is probably not the bottleneck.
| Test | Implementation | Time | Compared to `fast_qr` |
| -------- | -------------- | ------------------ | --------------------- |
| **V03H** | fuqr | 71.225 - 73.230 µs | ~1.01x slower |
| | qrcode | 541.65 - 569.61 µs | ~7.8x slower |
| | fast_qr | 70.581 - 72.627 µs | 1.0 (Fastest) |
| **V10H** | fuqr | 365.81 - 372.89 µs | ~1.4x slower |
| | qrcode | 2.2897 - 2.3480 ms | ~8.7x slower |
| | fast_qr | 262.86 - 270.61 µs | 1.0 (Fastest) |
| **V40H** | fuqr | 3.0942 - 3.1684 ms | ~1.3x slower |
| | qrcode | 21.502 - 21.952 ms | ~8.8x slower |
| | fast_qr | 2.4293 - 2.4919 ms | 1.0 (Fastest) |