Skip to content

Commit 1e01cec

Browse files
1313Copilot
andcommitted
feat!: drop CGImage and CMTime duplicates, re-export from apple-cf
BREAKING CHANGE: ScreenshotManager::capture now returns apple_cf::cg::CGImage (was a private nominal duplicate); screencapturekit::cm::CMTime is now a re-export of apple_cf::cm::CMTime (was a private nominal duplicate). Both changes eliminate cross-crate type conversions for downstream consumers chaining ScreenCaptureKit -> ImageIO / VideoToolbox / etc. Bumps version 3.1.4 -> 4.0.0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d0951f7 commit 1e01cec

16 files changed

Lines changed: 167 additions & 468 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [4.0.0] - 2026-05-18
11+
12+
### Changed
13+
14+
- [**breaking**] `ScreenshotManager::capture_image` now returns `apple_cf::cg::CGImage` (was a private nominal duplicate)
15+
- [**breaking**] `screencapturekit::cm::CMTime` is now a re-export of `apple_cf::cm::CMTime` (was a private nominal duplicate)
16+
- both changes eliminate cross-crate type conversions for downstream consumers chaining `ScreenCaptureKit``ImageIO` / `VideoToolbox` / related Apple frameworks
17+
1018
## [3.1.4] - 2026-05-17
1119

1220
### Fixed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "screencapturekit"
3-
version = "3.1.4"
3+
version = "4.0.0"
44
edition = "2021"
55
license = "MIT OR Apache-2.0"
66
homepage = "https://github.com/doom-fish/screencapturekit-rs"
@@ -66,7 +66,7 @@ macos_26_0 = ["macos_15_2"]
6666
# and Cargo's `[patch.crates-io]` will redirect to your sibling paths.
6767
# This lets `cargo install screencapturekit` and `release-plz` work from
6868
# a clean checkout without requiring the sibling repos.
69-
apple-cf = { version = ">=0.6, <0.8", default-features = false, features = ["cg", "iosurface", "dispatch", "cv", "cm"] }
69+
apple-cf = { version = ">=0.6, <0.9", default-features = false, features = ["cg", "iosurface", "dispatch", "cv", "cm"] }
7070
apple-metal = { version = ">=0.6, <0.9", default-features = false, features = ["iosurface"] }
7171

7272
[dev-dependencies]

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
```toml
4545
[dependencies]
46-
screencapturekit = "3"
46+
screencapturekit = "4"
4747
```
4848

4949
Opt-in features (additive):
@@ -62,7 +62,7 @@ Opt-in features (additive):
6262
`macos_*` features are **cumulative** — enabling `macos_15_0` automatically enables every earlier version. Pick the highest version your minimum-supported macOS will satisfy:
6363

6464
```toml
65-
screencapturekit = { version = "3", features = ["async", "macos_15_0"] }
65+
screencapturekit = { version = "4", features = ["async", "macos_15_0"] }
6666
```
6767

6868
> **Upgrading from 1.x?** See [`docs/MIGRATION.md`](docs/MIGRATION.md#migrating-from-1x-to-20)
@@ -206,7 +206,7 @@ custom executor — the binding does **not** spawn its own runtime.
206206
# filter: &screencapturekit::stream::content_filter::SCContentFilter,
207207
# config: &screencapturekit::stream::configuration::SCStreamConfiguration,
208208
# ) -> Result<(), Box<dyn std::error::Error>> {
209-
use screencapturekit::screenshot_manager::SCScreenshotManager;
209+
use screencapturekit::screenshot_manager::{CGImageExt, SCScreenshotManager};
210210
211211
let img = SCScreenshotManager::capture_image(filter, config)?;
212212
let pixels = img.bgra_data()?; // native BGRA — skips R↔B swap
@@ -398,7 +398,7 @@ sampling profiler; nearly all CPU lives in Apple's `SkyLight` /
398398
**Hot-path tips:**
399399

400400
- Prefer `BGRA` to skip the per-pixel R↔B swap when uploading to Metal /
401-
wgpu / ffmpeg (`SCScreenshotManager::bgra_data` is ~5% faster than `rgba_data`).
401+
wgpu / ffmpeg (`CGImageExt::bgra_data` is ~5% faster than `rgba_data`).
402402
- Reuse a `Vec<u8>` across screenshots with the `*_data_into` variants
403403
(saves a ~33 MB allocation per 4K frame — new in 2.1).
404404
- When iterating many windows / displays / apps, use the batched
@@ -461,7 +461,7 @@ The 2.0 highlights:
461461
dropping symbols)
462462

463463
2.1 added the `bgra_data_into` / `rgba_data_into` buffer-reuse APIs and a
464-
native-BGRA fast path on `SCScreenshotManager` — both are non-breaking.
464+
native-BGRA fast path on captured `CGImage`s — both are non-breaking.
465465

466466
## Contributing
467467

benches/hotspots.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
1111
use screencapturekit::cm::CMSampleBuffer;
1212
use screencapturekit::cv::CVPixelBufferLockFlags;
1313
use screencapturekit::prelude::*;
14+
use screencapturekit::screenshot_manager::CGImageExt;
1415
use screencapturekit::shareable_content::SCShareableContent;
1516
use screencapturekit::stream::configuration::SCStreamConfiguration;
1617
use screencapturekit::stream::content_filter::SCContentFilter;

examples/05_screenshot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#[cfg(feature = "macos_14_0")]
1313
use screencapturekit::prelude::*;
1414
#[cfg(feature = "macos_14_0")]
15-
use screencapturekit::screenshot_manager::SCScreenshotManager;
15+
use screencapturekit::screenshot_manager::{CGImageExt, SCScreenshotManager};
1616

1717
#[cfg(not(feature = "macos_14_0"))]
1818
fn main() {

examples/22_tauri_app/src-tauri/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use base64::{engine::general_purpose::STANDARD, Engine};
77
use screencapturekit::prelude::*;
8-
use screencapturekit::screenshot_manager::SCScreenshotManager;
8+
use screencapturekit::screenshot_manager::{CGImageExt, SCScreenshotManager};
99
use serde::{Deserialize, Serialize};
1010

1111
/// Display information returned to the frontend

examples/24_batched_apis_showcase.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
use screencapturekit::cm::CMSampleBuffer;
3333
use screencapturekit::cm::CMSampleBufferSCExt;
3434
use screencapturekit::prelude::*;
35-
use screencapturekit::screenshot_manager::SCScreenshotManager;
35+
use screencapturekit::screenshot_manager::{CGImageExt, SCScreenshotManager};
3636
use std::sync::atomic::{AtomicUsize, Ordering};
3737
use std::sync::{Arc, Mutex};
3838
use std::time::{Duration, Instant};

src/async_api.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,8 @@ extern "C" fn screenshot_image_callback(
624624
);
625625
}
626626
} else if !image_ptr.is_null() {
627-
let image = crate::screenshot_manager::CGImage::from_ptr(image_ptr);
627+
// SAFETY: the Swift bridge hands back a retained `CGImageRef` on success.
628+
let image = unsafe { crate::screenshot_manager::cgimage_from_retained_ptr(image_ptr) };
628629
// SAFETY: `user_data` is the one-shot completion context from `AsyncCompletion::create()`; Swift invokes this callback exactly once, so the pointer is still valid.
629630
unsafe { AsyncCompletion::complete_ok(user_data, image) };
630631
} else {

src/cm/sample_buffer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ impl CMSampleBufferExt for CMSampleBuffer {
576576
// Safety: the Swift bridge returns a retained CGImage on
577577
// success; passing it straight to CGImage::from_raw takes
578578
// ownership of that refcount.
579-
Ok(apple_cf::cg::CGImage::from_raw(ptr as *mut std::ffi::c_void))
579+
Ok(apple_cf::cg::CGImage::from_raw(ptr.cast_mut()))
580580
} else {
581581
Err(status)
582582
}

src/cm/time.rs

Lines changed: 1 addition & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,7 @@
33
use std::ffi::c_void;
44
use std::fmt;
55

6-
/// `CMTime` representation matching Core Media's `CMTime`
7-
///
8-
/// Represents a rational time value with a 64-bit numerator and 32-bit denominator.
9-
///
10-
/// # Examples
11-
///
12-
/// ```
13-
/// use screencapturekit::cm::CMTime;
14-
///
15-
/// // Create a time of 1 second (30/30)
16-
/// let time = CMTime::new(30, 30);
17-
/// assert_eq!(time.as_seconds(), Some(1.0));
18-
///
19-
/// // Create a time of 2.5 seconds at 1000 Hz timescale
20-
/// let time = CMTime::new(2500, 1000);
21-
/// assert_eq!(time.value, 2500);
22-
/// assert_eq!(time.timescale, 1000);
23-
/// assert_eq!(time.as_seconds(), Some(2.5));
24-
/// ```
25-
#[repr(C)]
26-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27-
pub struct CMTime {
28-
pub value: i64,
29-
pub timescale: i32,
30-
pub flags: u32,
31-
pub epoch: i64,
32-
}
33-
34-
impl std::hash::Hash for CMTime {
35-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
36-
self.value.hash(state);
37-
self.timescale.hash(state);
38-
self.flags.hash(state);
39-
self.epoch.hash(state);
40-
}
41-
}
6+
pub use apple_cf::cm::CMTime;
427

438
/// Sample timing information
449
///
@@ -160,124 +125,6 @@ impl fmt::Display for CMSampleTimingInfo {
160125
}
161126
}
162127

163-
impl CMTime {
164-
pub const ZERO: Self = Self {
165-
value: 0,
166-
timescale: 0,
167-
flags: 1,
168-
epoch: 0,
169-
};
170-
171-
pub const INVALID: Self = Self {
172-
value: 0,
173-
timescale: 0,
174-
flags: 0,
175-
epoch: 0,
176-
};
177-
178-
pub const fn new(value: i64, timescale: i32) -> Self {
179-
Self {
180-
value,
181-
timescale,
182-
flags: 1,
183-
epoch: 0,
184-
}
185-
}
186-
187-
pub const fn is_valid(&self) -> bool {
188-
self.flags & 0x1 != 0
189-
}
190-
191-
/// Check if this time represents zero
192-
pub const fn is_zero(&self) -> bool {
193-
self.value == 0 && self.is_valid()
194-
}
195-
196-
/// Check if this time is indefinite
197-
pub const fn is_indefinite(&self) -> bool {
198-
self.flags & 0x2 != 0
199-
}
200-
201-
/// Check if this time is positive infinity
202-
pub const fn is_positive_infinity(&self) -> bool {
203-
self.flags & 0x4 != 0
204-
}
205-
206-
/// Check if this time is negative infinity
207-
pub const fn is_negative_infinity(&self) -> bool {
208-
self.flags & 0x8 != 0
209-
}
210-
211-
/// Check if this time has been rounded
212-
pub const fn has_been_rounded(&self) -> bool {
213-
self.flags & 0x10 != 0
214-
}
215-
216-
/// Compare two times for equality (value and timescale)
217-
pub const fn equals(&self, other: &Self) -> bool {
218-
if !self.is_valid() || !other.is_valid() {
219-
return false;
220-
}
221-
self.value == other.value && self.timescale == other.timescale
222-
}
223-
224-
/// Create a time representing positive infinity
225-
pub const fn positive_infinity() -> Self {
226-
Self {
227-
value: 0,
228-
timescale: 0,
229-
flags: 0x5, // kCMTimeFlags_Valid | kCMTimeFlags_PositiveInfinity
230-
epoch: 0,
231-
}
232-
}
233-
234-
/// Create a time representing negative infinity
235-
pub const fn negative_infinity() -> Self {
236-
Self {
237-
value: 0,
238-
timescale: 0,
239-
flags: 0x9, // kCMTimeFlags_Valid | kCMTimeFlags_NegativeInfinity
240-
epoch: 0,
241-
}
242-
}
243-
244-
/// Create an indefinite time
245-
pub const fn indefinite() -> Self {
246-
Self {
247-
value: 0,
248-
timescale: 0,
249-
flags: 0x3, // kCMTimeFlags_Valid | kCMTimeFlags_Indefinite
250-
epoch: 0,
251-
}
252-
}
253-
254-
pub fn as_seconds(&self) -> Option<f64> {
255-
if self.is_valid() && self.timescale != 0 {
256-
// Precision loss is acceptable for time conversion to seconds
257-
#[allow(clippy::cast_precision_loss)]
258-
Some(self.value as f64 / f64::from(self.timescale))
259-
} else {
260-
None
261-
}
262-
}
263-
}
264-
265-
impl Default for CMTime {
266-
fn default() -> Self {
267-
Self::INVALID
268-
}
269-
}
270-
271-
impl fmt::Display for CMTime {
272-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273-
if let Some(seconds) = self.as_seconds() {
274-
write!(f, "{seconds:.3}s")
275-
} else {
276-
write!(f, "invalid")
277-
}
278-
}
279-
}
280-
281128
/// `CMClock` wrapper for synchronization clock
282129
///
283130
/// Represents a Core Media clock used for time synchronization.

0 commit comments

Comments
 (0)