Skip to content

Commit 42034c5

Browse files
Add support for cancelling encoding (image-rs#83)
1 parent 847460e commit 42034c5

File tree

10 files changed

+383
-95
lines changed

10 files changed

+383
-95
lines changed

src/encode/bc.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
encode::write_util::for_each_f32_rgba_rows,
66
n4,
77
util::{self, clamp_0_1},
8-
EncodingError, Report,
8+
EncodingError,
99
};
1010

1111
use super::{
@@ -26,7 +26,7 @@ fn block_universal<
2626
image,
2727
writer,
2828
options,
29-
mut progress,
29+
progress,
3030
..
3131
} = args;
3232
let width = image.width() as usize;
@@ -46,11 +46,13 @@ fn block_universal<
4646
};
4747
let block_count = util::div_ceil(width, BLOCK_WIDTH) * util::div_ceil(height, BLOCK_HEIGHT);
4848
let mut block_index: usize = 0;
49-
let mut report_block = || {
50-
if block_index % report_frequency == 0 {
51-
progress.report(block_index as f32 / block_count as f32);
52-
}
49+
let mut report_block = || -> Result<(), EncodingError> {
50+
progress.checked_report_if(
51+
block_index % report_frequency == 0,
52+
block_index as f32 / block_count as f32,
53+
)?;
5354
block_index += 1;
55+
Ok(())
5456
};
5557

5658
for_each_f32_rgba_rows(image, BLOCK_HEIGHT, |rows| {
@@ -62,7 +64,7 @@ fn block_universal<
6264
let encoded = &mut encoded_buffer[block_index];
6365

6466
encode_block(block, width, &options, encoded);
65-
report_block();
67+
report_block()?;
6668
}
6769

6870
// handle last partial block
@@ -83,13 +85,13 @@ fn block_universal<
8385

8486
let encoded = &mut encoded_buffer[block_index];
8587
encode_block(&block_data, BLOCK_WIDTH, &options, encoded);
86-
report_block();
88+
report_block()?;
8789
}
8890

89-
writer.write_all(cast::as_bytes(&encoded_buffer))
90-
})?;
91+
writer.write_all(cast::as_bytes(&encoded_buffer))?;
9192

92-
Ok(())
93+
Ok(())
94+
})
9395
}
9496

9597
fn get_4x4_rgba(data: &[[f32; 4]], row_pitch: usize) -> [[f32; 4]; 16] {

src/encode/bi_planar.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use super::{
99
use crate::{
1010
cast::{self, ToLe},
1111
encode::write_util::for_each_f32_rgba_rows,
12-
util, yuv10, yuv16, yuv8, EncodingError, Report,
12+
util, yuv10, yuv16, yuv8, EncodingError,
1313
};
1414

1515
#[allow(clippy::type_complexity)]
@@ -24,7 +24,7 @@ fn bi_planar_universal<P1: ToLe + cast::Castable + Default + Copy, P2: ToLe + ca
2424
image,
2525
writer,
2626
options,
27-
mut progress,
27+
progress,
2828
..
2929
} = args;
3030
let width = image.width() as usize;
@@ -43,11 +43,12 @@ fn bi_planar_universal<P1: ToLe + cast::Castable + Default + Copy, P2: ToLe + ca
4343
let line_group_count = util::div_ceil(height, BLOCK_HEIGHT);
4444
let report_frequency = util::div_ceil(1024 * 1024, width * BLOCK_HEIGHT);
4545
let mut group_index = 0;
46-
for_each_f32_rgba_rows(image, BLOCK_HEIGHT, |rows| {
47-
if group_index % report_frequency == 0 {
48-
// occasionally report progress
49-
progress.report(group_index as f32 / line_group_count as f32);
50-
}
46+
for_each_f32_rgba_rows(image, BLOCK_HEIGHT, |rows| -> Result<(), EncodingError> {
47+
// occasionally report progress
48+
progress.checked_report_if(
49+
group_index % report_frequency == 0,
50+
group_index as f32 / line_group_count as f32,
51+
)?;
5152
group_index += 1;
5253

5354
// handle full blocks
@@ -70,9 +71,12 @@ fn bi_planar_universal<P1: ToLe + cast::Castable + Default + Copy, P2: ToLe + ca
7071
}
7172

7273
P1::to_le(&mut plane1_buffer);
73-
writer.write_all(cast::as_bytes(&plane1_buffer))
74+
writer.write_all(cast::as_bytes(&plane1_buffer))?;
75+
76+
Ok(())
7477
})?;
7578

79+
progress.check_cancelled()?;
7680
P2::to_le(&mut plane2);
7781
writer.write_all(cast::as_bytes(&plane2))?;
7882

src/encode/encoder.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use bitflags::bitflags;
44

55
use crate::{
66
cast, encode::write_util::for_each_chunk, ColorFormat, ColorFormatSet, EncodingError,
7-
ImageView, Precision, Progress, Report,
7+
ImageView, Precision, Progress,
88
};
99

1010
use super::{
@@ -14,14 +14,14 @@ use super::{
1414
pub(crate) struct Args<'a, 'b, 'c, 'd> {
1515
pub image: ImageView<'a>,
1616
pub writer: &'b mut dyn Write,
17-
pub progress: Option<&'c mut Progress<'d>>,
17+
pub progress: &'c mut Progress<'d>,
1818
pub options: EncodeOptions,
1919
}
2020
impl<'a, 'b, 'c, 'd> Args<'a, 'b, 'c, 'd> {
2121
fn from(
2222
image: ImageView<'a>,
2323
writer: &'b mut dyn Write,
24-
progress: Option<&'c mut Progress<'d>>,
24+
progress: &'c mut Progress<'d>,
2525
options: EncodeOptions,
2626
) -> Result<Self, EncodingError> {
2727
Ok(Self {
@@ -245,7 +245,7 @@ impl EncoderSet {
245245
&self,
246246
writer: &mut dyn Write,
247247
image: ImageView,
248-
progress: Option<&mut Progress>,
248+
progress: &mut Progress,
249249
options: &EncodeOptions,
250250
) -> Result<(), EncodingError> {
251251
let encoder = self.pick_encoder(image.color(), options);
@@ -258,12 +258,12 @@ fn copy_directly(args: Args) -> Result<(), EncodingError> {
258258
let Args {
259259
image,
260260
writer,
261-
mut progress,
261+
progress,
262262
..
263263
} = args;
264264
let color = image.color();
265265

266-
progress.report(0.0);
266+
progress.checked_report(0.0)?;
267267

268268
// sometimes we can always just write everything directly without any processing
269269
if image.is_contiguous() && (cfg!(target_endian = "little") || color.precision == Precision::U8)

src/encode/mod.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,33 +110,60 @@ pub(crate) const fn get_encoders(format: Format) -> Option<EncoderSet> {
110110
})
111111
}
112112

113+
/// Encodes a single surfaces in the given format and writes the encoded pixel
114+
/// data to the given writer.
115+
///
116+
/// If an error is returned (or the operation is cancelled), the writer may be
117+
/// in an inconsistent state. Some, all, or none of the pixel data may have been
118+
/// written.
119+
///
120+
/// If `progress` is cancelled before this function returns,
121+
/// [`EncodingError::Cancelled`] is returned (unless another error occurs first).
122+
/// Passing `None` as `progress` is equivalent to [`Progress::none()`].
123+
///
124+
/// ## Panics
125+
///
126+
/// Panics if:
127+
///
128+
/// 1. `writer` panics during writes.
129+
/// 2. The underlying reporter function of `progress` panics (if given).
130+
/// 3. An allocation fails.
113131
pub fn encode(
114132
writer: &mut dyn Write,
115133
image: ImageView,
116134
format: Format,
117135
progress: Option<&mut Progress>,
118136
options: &EncodeOptions,
119137
) -> Result<(), EncodingError> {
138+
let mut no_reporting = Progress::none();
139+
let progress = progress.unwrap_or(&mut no_reporting);
140+
141+
// ending quickly if cancelled is a good property to have
142+
progress.check_cancelled()?;
143+
120144
#[cfg(feature = "rayon")]
121145
if options.parallel {
122146
return encode_parallel(writer, image, format, progress, options);
123147
}
124148

125149
let encoders = get_encoders(format).ok_or(EncodingError::UnsupportedFormat(format))?;
126-
encoders.encode(writer, image, progress, options)
150+
encoders.encode(writer, image, progress, options)?;
151+
152+
// error if the operation was cancelled
153+
progress.check_cancelled()
127154
}
128155

129156
#[cfg(feature = "rayon")]
130157
fn encode_parallel(
131158
writer: &mut dyn Write,
132159
image: ImageView,
133160
format: Format,
134-
mut progress: Option<&mut Progress>,
161+
mut progress: &mut Progress,
135162
options: &EncodeOptions,
136163
) -> Result<(), EncodingError> {
137164
use rayon::iter::{IntoParallelIterator, ParallelIterator};
138165

139-
use crate::{ParallelProgress, PixelInfo, Report, SplitView};
166+
use crate::{ParallelProgress, PixelInfo, SplitView};
140167

141168
let mut options = options.clone();
142169
// don't cause an infinite loop
@@ -146,7 +173,7 @@ fn encode_parallel(
146173

147174
// optimization for single fragment
148175
if let Some(single) = split.single() {
149-
return encode(writer, single, format, progress, &options);
176+
return encode(writer, single, format, Some(progress), &options);
150177
}
151178

152179
// Prepare the parallel progress reporter. The +1 ensures that 100% is
@@ -160,6 +187,7 @@ fn encode_parallel(
160187
.into_par_iter()
161188
.map(|fragment_index| -> Result<Vec<u8>, EncodingError> {
162189
let fragment = split.get(fragment_index).expect("invalid fragment index");
190+
parallel_progress.check_cancelled()?;
163191

164192
// allocate exactly the right amount of memory
165193
let bytes: usize = pixel_info
@@ -169,10 +197,12 @@ fn encode_parallel(
169197
.expect("too many bytes");
170198
let mut buffer: Vec<u8> = Vec::with_capacity(bytes);
171199

172-
// encode the fragment without any progress reporting.
173-
// progress will be reported per fragment
200+
// Encode the fragment without any progress reporting or cancellation.
201+
// Fragments are typically very small and encoded quickly, so
202+
// reporting progress or checking for cancellation is not necessary.
174203
encode(&mut buffer, fragment, format, None, &options)?;
175204

205+
parallel_progress.check_cancelled()?;
176206
parallel_progress.submit(fragment.height() as u64);
177207

178208
debug_assert_eq!(buffer.len(), bytes);
@@ -182,13 +212,12 @@ fn encode_parallel(
182212

183213
let encoded_fragments = result?;
184214
for fragment in encoded_fragments {
215+
progress.check_cancelled()?;
185216
writer.write_all(&fragment)?;
186217
}
187218

188-
// Report 100%
189-
progress.report(1.0);
190-
191-
Ok(())
219+
// report completion
220+
progress.checked_report(1.0)
192221
}
193222

194223
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

src/encode/sub_sampled.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{as_rgba_f32, cast, ch, n1, n8, util, yuv16, yuv8, EncodingError, Report};
1+
use crate::{as_rgba_f32, cast, ch, n1, n8, util, yuv16, yuv8, EncodingError};
22

33
use super::encoder::{Args, Encoder, EncoderSet, Flags};
44

@@ -38,7 +38,7 @@ where
3838
let Args {
3939
image,
4040
writer,
41-
mut progress,
41+
progress,
4242
..
4343
} = args;
4444
let color = image.color();
@@ -62,10 +62,11 @@ where
6262
debug_assert!(y_line.len() == width * bytes_per_pixel);
6363

6464
for chunk in y_line.chunks(chunk_size) {
65-
if chunk_index % 4096 == 0 {
66-
// occasionally report progress
67-
progress.report(chunk_index as f32 / chunk_count as f32);
68-
}
65+
// occasionally report progress
66+
progress.checked_report_if(
67+
chunk_index % 4096 == 0,
68+
chunk_index as f32 / chunk_count as f32,
69+
)?;
6970
chunk_index += 1;
7071

7172
let pixels = chunk.len() / bytes_per_pixel;

0 commit comments

Comments
 (0)