Skip to content

Commit 04312f8

Browse files
Minimal implementation of QImage.
Just enough to make the qt-tutorial for KDAB Training day work.
1 parent 8452e25 commit 04312f8

File tree

8 files changed

+257
-1
lines changed

8 files changed

+257
-1
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// clang-format off
2+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
// clang-format on
4+
// SPDX-FileContributor: Leon Matthes <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT OR Apache-2.0
7+
#pragma once
8+
9+
#ifdef CXX_QT_GUI_FEATURE
10+
11+
#include <QtGui/QImage>
12+
13+
#include "rust/cxx.h"
14+
15+
namespace rust {
16+
17+
// QImage has a move constructor, however it is basically trivial.
18+
template<>
19+
struct IsRelocatable<QImage> : ::std::true_type
20+
{
21+
};
22+
23+
namespace cxxqtlib1 {
24+
using QImageFormat = QImage::Format;
25+
26+
QImage
27+
qimageInitFromData(const QByteArray& data, const char* format);
28+
29+
} // namespace cxxqtlib1
30+
} // namespace rust
31+
#endif

crates/cxx-qt-lib-headers/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ pub fn write_headers(directory: impl AsRef<Path>) {
7171
"qguiapplication.h",
7272
),
7373
#[cfg(feature = "qt_gui")]
74+
(include_str!("../include/gui/qimage.h"), "qimage.h"),
75+
#[cfg(feature = "qt_gui")]
7476
(include_str!("../include/gui/qvector2d.h"), "qvector2d.h"),
7577
#[cfg(feature = "qt_gui")]
7678
(include_str!("../include/gui/qvector3d.h"), "qvector3d.h"),

crates/cxx-qt-lib/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ fn main() {
158158
"core/qvector/qvector_qcolor",
159159
"gui/qcolor",
160160
"gui/qguiapplication",
161+
"gui/qimage",
161162
"gui/qvector2d",
162163
"gui/qvector3d",
163164
"gui/qvector4d",
@@ -225,6 +226,7 @@ fn main() {
225226
cpp_files.extend([
226227
"gui/qcolor",
227228
"gui/qguiapplication",
229+
"gui/qimage",
228230
"gui/qvector2d",
229231
"gui/qvector3d",
230232
"gui/qvector4d",

crates/cxx-qt-lib/src/core/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
mod qbytearray;
77
pub use qbytearray::QByteArray;
8+
pub use qbytearray::QByteArrayCursor;
89

910
mod qcoreapplication;
1011
pub use qcoreapplication::QCoreApplication;

crates/cxx-qt-lib/src/core/qbytearray.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55
use cxx::{type_id, ExternType};
6-
use std::mem::MaybeUninit;
6+
use std::{
7+
io::{Cursor, Seek, Write},
8+
mem::MaybeUninit,
9+
};
710

811
#[cxx::bridge]
912
mod ffi {
@@ -122,6 +125,97 @@ impl AsRef<[u8]> for QByteArray {
122125
}
123126
}
124127

128+
/// A QByteArray cursor allows seeking inside a QByteArray
129+
/// and writing to it.
130+
/// Comparable with std::io::Cursor.
131+
pub struct QByteArrayCursor {
132+
inner: QByteArray,
133+
position: isize,
134+
}
135+
136+
impl QByteArrayCursor {
137+
/// Constructs a new QByteArrayCursor at location 0.
138+
/// Like std::io::Cursor<Vec<u8>> this will overwrite
139+
/// existing data in the QByteArray before starting to resize.
140+
pub fn new(bytearray: QByteArray) -> Self {
141+
Self {
142+
inner: bytearray,
143+
position: 0,
144+
}
145+
}
146+
147+
/// Returns a reference to the underlying QByteArray.
148+
pub fn into_inner(self) -> QByteArray {
149+
self.inner
150+
}
151+
}
152+
153+
/// Like the implementation of Cursor<Vec<u8>>, the Cursor<QByteArray>
154+
/// will overwrite existing data in the QByteArray.
155+
/// The QByteArray is resized as needed.
156+
impl Write for QByteArrayCursor {
157+
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
158+
let length_after_append = buf.len() as isize + self.position;
159+
let bytearray_length = self.inner.len();
160+
if length_after_append > bytearray_length {
161+
self.inner.resize(length_after_append);
162+
}
163+
164+
let bytes = self.inner.as_mut_slice();
165+
166+
// If the resize operation failed, then we need to do additional
167+
// error handling.
168+
// Luckily we can just leave this up to the Cursor<&mut [u8]> implementation.
169+
let mut bytes_cursor = Cursor::new(bytes);
170+
bytes_cursor.set_position(self.position as u64);
171+
172+
bytes_cursor.write(buf).map(|bytes_written| {
173+
self.position += bytes_written as isize;
174+
bytes_written
175+
})
176+
}
177+
178+
fn flush(&mut self) -> std::io::Result<()> {
179+
// Nothing to flush, we always write everything
180+
Ok(())
181+
}
182+
}
183+
184+
impl AsRef<QByteArray> for QByteArrayCursor {
185+
fn as_ref(&self) -> &QByteArray {
186+
&self.inner
187+
}
188+
}
189+
190+
impl Seek for QByteArrayCursor {
191+
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
192+
use std::io::{Error, ErrorKind, SeekFrom};
193+
let (base_pos, offset) = match pos {
194+
SeekFrom::Start(n) => (
195+
0_usize,
196+
n.try_into().map_err(|_err| {
197+
Error::new(
198+
ErrorKind::InvalidInput,
199+
"invalid seek to an overflowing position",
200+
)
201+
})?,
202+
),
203+
SeekFrom::End(n) => (self.inner.len() as usize, n),
204+
SeekFrom::Current(n) => (self.position as usize, n),
205+
};
206+
match base_pos.checked_add_signed(offset as isize) {
207+
Some(n) => {
208+
self.position = (n as isize).min(self.inner.len());
209+
Ok(self.position as u64)
210+
}
211+
None => Err(Error::new(
212+
ErrorKind::InvalidInput,
213+
"invalid seek to a negative or overflowing position",
214+
)),
215+
}
216+
}
217+
}
218+
125219
impl Clone for QByteArray {
126220
/// Constructs a copy of other.
127221
///

crates/cxx-qt-lib/src/gui/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ pub use qvector3d::QVector3D;
1717

1818
mod qvector4d;
1919
pub use qvector4d::QVector4D;
20+
21+
mod qimage;
22+
pub use qimage::QImage;

crates/cxx-qt-lib/src/gui/qimage.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// clang-format off
2+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
3+
// clang-format on
4+
// SPDX-FileContributor: Leon Matthes <[email protected]>
5+
//
6+
// SPDX-License-Identifier: MIT OR Apache-2.0
7+
8+
#ifdef CXX_QT_GUI_FEATURE
9+
10+
#include "cxx-qt-lib/qimage.h"
11+
#include "../assertion_utils.h"
12+
13+
// A QImage inherits from QPaintDevice.
14+
15+
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
16+
// QPaintDevice in Qt5 contains two things:
17+
// 1. ushort painters; (due to the following field this has padding to make it
18+
// 64-bit long)
19+
// 2. QPaintDevicePrivate *reserved;
20+
// Then QImage adds an additional field:
21+
// 3. QImageData *d;
22+
// For a total of 3 pointers in length.
23+
// Because of the added v-table, it's a total of 4 pointers in size.
24+
assert_alignment_and_size(QImage,
25+
alignof(::std::size_t),
26+
sizeof(::std::size_t) * 4);
27+
#else
28+
// In Qt6 the QPaintDevice doesn't contain the `reserved` pointer, making it 1
29+
// pointer smaller
30+
assert_alignment_and_size(QImage,
31+
alignof(::std::size_t),
32+
sizeof(::std::size_t) * 3);
33+
#endif
34+
35+
namespace rust {
36+
namespace cxxqtlib1 {
37+
38+
QImage
39+
qimageInitFromData(const QByteArray& data, const char* format)
40+
{
41+
return QImage::fromData(data, format);
42+
}
43+
44+
}
45+
}
46+
47+
#endif

crates/cxx-qt-lib/src/gui/qimage.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Leon Matthes <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
use crate::QByteArray;
7+
use cxx::{type_id, ExternType};
8+
use std::{
9+
ffi::{c_char, CString},
10+
mem::MaybeUninit,
11+
};
12+
13+
#[cxx::bridge]
14+
mod ffi {
15+
16+
unsafe extern "C++" {
17+
include!("cxx-qt-lib/qbytearray.h");
18+
type QByteArray = crate::QByteArray;
19+
20+
include!("cxx-qt-lib/qimage.h");
21+
type QImage = super::QImage;
22+
23+
#[rust_name = "is_null"]
24+
fn isNull(self: &QImage) -> bool;
25+
}
26+
27+
#[namespace = "rust::cxxqtlib1"]
28+
unsafe extern "C++" {
29+
include!("cxx-qt-lib/common.h");
30+
31+
#[doc(hidden)]
32+
#[rust_name = "qimage_drop"]
33+
fn drop(image: &mut QImage);
34+
35+
#[doc(hidden)]
36+
#[rust_name = "qimage_init_from_data"]
37+
unsafe fn qimageInitFromData(data: &QByteArray, format: *const c_char) -> QImage;
38+
}
39+
}
40+
41+
#[repr(C)]
42+
pub struct QImage {
43+
// Static checks on the C++ side ensure this is true.
44+
// See qcolor.cpp
45+
#[cfg(qt_version_major = "5")]
46+
_data: MaybeUninit<[usize; 4]>,
47+
#[cfg(qt_version_major = "6")]
48+
_data: MaybeUninit<[usize; 3]>,
49+
}
50+
51+
// Safety:
52+
//
53+
// Static checks on the C++ side to ensure the size & alignment is the same.
54+
unsafe impl ExternType for QImage {
55+
type Id = type_id!("QImage");
56+
type Kind = cxx::kind::Trivial;
57+
}
58+
59+
impl Drop for QImage {
60+
fn drop(&mut self) {
61+
ffi::qimage_drop(self);
62+
}
63+
}
64+
65+
impl QImage {
66+
pub fn from_data(data: &QByteArray, format: &str) -> Option<Self> {
67+
let format = CString::new(format).ok()?;
68+
let image = unsafe { ffi::qimage_init_from_data(data, format.as_ptr() as *const c_char) };
69+
70+
if !image.is_null() {
71+
Some(image)
72+
} else {
73+
None
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)