From d508253aa9e4b449afe480bd6034a4acd044d825 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Tue, 30 Jan 2024 13:19:53 -0600 Subject: [PATCH] wip: inop dx_film_edge port and filtered reader --- src/barcode_format.rs | 2 + src/binary_bitmap.rs | 2 +- src/buffered_image_luminance_source.rs | 33 +++- src/common/cpp_essentials/pattern.rs | 7 +- src/common/global_histogram_binarizer.rs | 53 ++++- src/filtered_image_reader.rs | 216 +++++++++++++++++++++ src/lib.rs | 3 + src/luma_luma_source.rs | 32 +++ src/luminance_source.rs | 5 + src/multi_format_reader.rs | 7 + src/oned/cpp/dxfilm_edge_reader.rs | 2 +- src/oned/cpp/one_d_reader.rs | 35 +--- src/oned/cpp/row_reader.rs | 2 +- src/planar_yuv_luminance_source.rs | 8 + src/rgb_luminance_source.rs | 12 ++ src/svg_luminance_source.rs | 8 + test_resources/blackbox/dxfilmedge-1/1.jpg | Bin 0 -> 25235 bytes test_resources/blackbox/dxfilmedge-1/1.txt | 1 + test_resources/blackbox/dxfilmedge-1/2.png | Bin 0 -> 16300 bytes test_resources/blackbox/dxfilmedge-1/2.txt | 1 + test_resources/blackbox/dxfilmedge-1/3.jpg | Bin 0 -> 1652 bytes test_resources/blackbox/dxfilmedge-1/3.txt | 1 + tests/dx_film_edge.rs | 27 +++ 23 files changed, 421 insertions(+), 36 deletions(-) create mode 100644 src/filtered_image_reader.rs create mode 100644 test_resources/blackbox/dxfilmedge-1/1.jpg create mode 100644 test_resources/blackbox/dxfilmedge-1/1.txt create mode 100644 test_resources/blackbox/dxfilmedge-1/2.png create mode 100644 test_resources/blackbox/dxfilmedge-1/2.txt create mode 100644 test_resources/blackbox/dxfilmedge-1/3.jpg create mode 100644 test_resources/blackbox/dxfilmedge-1/3.txt create mode 100644 tests/dx_film_edge.rs diff --git a/src/barcode_format.rs b/src/barcode_format.rs index 5ca636c..b1211e2 100644 --- a/src/barcode_format.rs +++ b/src/barcode_format.rs @@ -119,6 +119,7 @@ impl Display for BarcodeFormat { BarcodeFormat::UPC_A => "upc a", BarcodeFormat::UPC_E => "upc e", BarcodeFormat::UPC_EAN_EXTENSION => "upc/ean extension", + BarcodeFormat::DXFilmEdge => "DXFilmEdge", _ => "unsuported", } ) @@ -166,6 +167,7 @@ impl From<&str> for BarcodeFormat { "upc e" | "upc_e" | "upce" => BarcodeFormat::UPC_E, "upc ean extension" | "upc extension" | "ean extension" | "upc/ean extension" | "upc_ean_extension" => BarcodeFormat::UPC_EAN_EXTENSION, + "DXFilmEdge" | "dxfilmedge" | "dx film edge" => BarcodeFormat::DXFilmEdge, _ => BarcodeFormat::UNSUPORTED_FORMAT, } } diff --git a/src/binary_bitmap.rs b/src/binary_bitmap.rs index 056c72b..3f4189e 100644 --- a/src/binary_bitmap.rs +++ b/src/binary_bitmap.rs @@ -32,7 +32,7 @@ use crate::{ pub struct BinaryBitmap { binarizer: B, - matrix: Option, + pub(crate) matrix: Option, } impl BinaryBitmap { diff --git a/src/buffered_image_luminance_source.rs b/src/buffered_image_luminance_source.rs index a736ec1..4d6bb8b 100644 --- a/src/buffered_image_luminance_source.rs +++ b/src/buffered_image_luminance_source.rs @@ -16,7 +16,7 @@ use std::rc::Rc; -use image::{DynamicImage, ImageBuffer, Luma}; +use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Pixel}; use imageproc::geometric_transformations::rotate_about_center; use crate::common::Result; @@ -108,6 +108,33 @@ impl LuminanceSource for BufferedImageLuminanceSource { pixels } + fn get_column(&self, x: usize) -> Vec { + let width = self.get_height(); // - self.left as usize; + + let pixels: Vec = || -> Option> { + Some( + self.image + .as_luma8()? + .rows() + .skip(self.top as usize) + .fold(Vec::default(), |mut acc, e| { + acc.push( + e.into_iter() + .nth(self.left as usize + x) + .unwrap_or(&Luma([0_u8])), + ); + acc + }) + .iter() + .map(|&p| p.0[0]) + .collect(), + ) + }() + .unwrap_or_default(); + + pixels + } + fn get_matrix(&self) -> Vec { if self.height == self.image.height() as usize && self.width == self.image.width() as usize { @@ -201,4 +228,8 @@ impl LuminanceSource for BufferedImageLuminanceSource { top: 0, }) } + + fn get_luma8_point(&self, x: usize, y: usize) -> u8 { + self.image.get_pixel(x as u32, y as u32).to_luma().0[0] + } } diff --git a/src/common/cpp_essentials/pattern.rs b/src/common/cpp_essentials/pattern.rs index 6e6b288..85f7b45 100644 --- a/src/common/cpp_essentials/pattern.rs +++ b/src/common/cpp_essentials/pattern.rs @@ -217,7 +217,7 @@ impl<'a> PatternView<'a> { pub fn isValidWithN(&self, n: usize) -> bool { !self.data.0.is_empty() && self.start <= self.current + self.start - && self.current + n <= (self.data.0.len()) + && self.current + n < (self.data.0.len()) /*return _data && _data >= _base && _data + n <= _end;*/ } pub fn isValid(&self) -> bool { @@ -271,7 +271,10 @@ impl<'a> PatternView<'a> { } pub fn extend(&mut self) { - self.count = std::cmp::max(0, self.data.len() - (self.current + self.start)) + self.count = std::cmp::max( + 0, + self.data.len() as isize - (self.current + self.start) as isize, + ) as usize } fn try_get_index(&self, index: isize) -> Option { diff --git a/src/common/global_histogram_binarizer.rs b/src/common/global_histogram_binarizer.rs index 0d193de..278291d 100644 --- a/src/common/global_histogram_binarizer.rs +++ b/src/common/global_histogram_binarizer.rs @@ -27,7 +27,7 @@ use once_cell::unsync::OnceCell; use crate::common::Result; use crate::{Binarizer, Exceptions, LuminanceSource}; -use super::{BitArray, BitMatrix}; +use super::{BitArray, BitMatrix, LineOrientation}; const LUMINANCE_BITS: usize = 5; const LUMINANCE_SHIFT: usize = 8 - LUMINANCE_BITS; @@ -107,8 +107,55 @@ impl Binarizer for GlobalHistogramBinarizer { Ok(Cow::Borrowed(row)) } - fn get_black_line(&self, l: usize, lt: super::LineOrientation) -> Result> { - unimplemented!() + fn get_black_line(&self, l: usize, lt: LineOrientation) -> Result> { + if lt == LineOrientation::Row { + self.get_black_row(l) + } else { + let col = self.black_column_cache[l].get_or_try_init(|| { + let source = self.get_luminance_source(); + let width = source.get_height(); + let mut col = BitArray::with_size(width); + + // self.initArrays(width); + let localLuminances = source.get_column(l); + let mut localBuckets = [0; LUMINANCE_BUCKETS]; //self.buckets.clone(); + for x in 0..width { + // for (int x = 0; x < width; x++) { + localBuckets[((localLuminances[x]) >> LUMINANCE_SHIFT) as usize] += 1; + } + let blackPoint = Self::estimateBlackPoint(&localBuckets)?; + + if width < 3 { + // Special case for very small images + for (x, lum) in localLuminances.iter().enumerate().take(width) { + // for x in 0..width { + // for (int x = 0; x < width; x++) { + if (*lum as u32) < blackPoint { + col.set(x); + } + } + } else { + let mut left = localLuminances[0]; // & 0xff; + let mut center = localLuminances[1]; // & 0xff; + for x in 1..width - 1 { + // for (int x = 1; x < width - 1; x++) { + let right = localLuminances[x + 1]; + // A simple -1 4 -1 box filter with a weight of 2. + if ((center as i64 * 4) - left as i64 - right as i64) / 2 + < blackPoint as i64 + { + col.set(x); + } + left = center; + center = right; + } + } + + Ok(col) + })?; + + Ok(Cow::Borrowed(col)) + } } // Does not sharpen the data, as this call is intended to only be used by 2D Readers. diff --git a/src/filtered_image_reader.rs b/src/filtered_image_reader.rs new file mode 100644 index 0000000..ff8dce1 --- /dev/null +++ b/src/filtered_image_reader.rs @@ -0,0 +1,216 @@ +use std::collections::HashMap; + +use crate::{Binarizer, BinaryBitmap, Exceptions, Luma8LuminanceSource, LuminanceSource, Reader}; + +use crate::common::{BitMatrix, Result}; + +pub const DEFAULT_DOWNSCALE_THRESHHOLD: usize = 500; +pub const DEFAULT_DOWNSCALE_FACTOR: usize = 3; + +/// Passed image data is ignored, only the image data +pub struct FilteredImageReader> { + reader: R, + source: Luma8LuminanceSource, + binarizer: B, +} + +impl> FilteredImageReader { + pub fn new>( + reader: R, + source: I, + binarizer: B, + ) -> Self { + Self { + reader, + source: source.into(), + binarizer, + } + } +} + +impl> Reader + for FilteredImageReader +{ + fn decode( + &mut self, + _image: &mut crate::BinaryBitmap, + ) -> crate::common::Result { + self.decode_with_hints(_image, &HashMap::default()) + } + + fn decode_with_hints( + &mut self, + _image: &mut crate::BinaryBitmap, + hints: &crate::DecodingHintDictionary, + ) -> crate::common::Result { + let pyramids = LumImagePyramid::new( + self.source.clone(), + DEFAULT_DOWNSCALE_THRESHHOLD, + DEFAULT_DOWNSCALE_FACTOR, + ) + .ok_or(Exceptions::ILLEGAL_ARGUMENT)?; + for layer in pyramids.layers { + let mut b = BinaryBitmap::new(self.binarizer.create_binarizer(layer)); + for close in [false, true] { + if close { + let Ok(_) = b.close() else { + continue; + }; + } + if let Ok(res) = self.reader.decode_with_hints(&mut b, hints) { + return Ok(res); + } else { + continue; + } + } + } + Err(Exceptions::NOT_FOUND) + } +} + +#[derive(Debug, Clone)] +struct LumImagePyramid { + buffers: Vec, + pub layers: Vec, +} + +impl Default for LumImagePyramid { + fn default() -> Self { + Self { + buffers: Default::default(), + layers: Default::default(), + } + } +} + +impl LumImagePyramid { + pub fn new(image: Luma8LuminanceSource, threshold: usize, factor: usize) -> Option { + let mut new_self = Self::default(); + + new_self.layers.push(image); + // TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425) + while threshold > 0 + && std::cmp::max( + new_self.layers.last()?.get_width(), + new_self.layers.last()?.get_height(), + ) > threshold + && std::cmp::min( + new_self.layers.last()?.get_width(), + new_self.layers.last()?.get_height(), + ) >= factor + { + new_self.add_layer_with_factor(factor).ok()?; + } + + if false { + // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a + // single symbol. If we start with the higher resolution, we get better (high res) position information. + // TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance. + new_self.layers.reverse(); + } + + Some(new_self) + } + + fn add_layer(&mut self) -> Result<()> { + let siv = self.layers.last().ok_or(Exceptions::ILLEGAL_ARGUMENT)?; + + self.buffers.push(Luma8LuminanceSource::with_empty_image( + siv.get_width() / N, + siv.get_height() / N, + )); + + let div = self + .buffers + .last_mut() + .ok_or(Exceptions::ILLEGAL_ARGUMENT)?; + + let div_height = div.get_height(); + let div_width = div.get_width(); + + let d_vec = div.get_matrix_mut(); + + for d in d_vec.iter_mut() { + for dy in 0..div_height { + // for (int dy = 0; dy < div.height(); ++dy){ + for dx in 0..div_width { + // for (int dx = 0; dx < div.width(); ++dx) { + let mut sum = (N * N) as u8 / 2; + for ty in 0..N { + // for (int ty = 0; ty < N; ++ty){ + for tx in 0..N { + // for (int tx = 0; tx < N; ++tx) { + sum += siv.get_luma8_point(dx * N + tx, dy * N + ty); + } + } + *d = sum / (N * N) as u8; + } + } + } + + self.layers.push( + self.buffers + .last() + .ok_or(Exceptions::ILLEGAL_ARGUMENT)? + .clone(), + ); + + Ok(()) + } + + fn add_layer_with_factor(&mut self, factor: usize) -> Result<()> { + // help the compiler's auto-vectorizer by hard-coding the scale factor + match factor { + 2 => self.add_layer::<2>(), + 3 => self.add_layer::<3>(), + 4 => self.add_layer::<4>(), + _ => Err(Exceptions::illegal_argument_with( + "Invalid ReaderOptions::downscaleFactor", + )), + } + } +} + +const SET_V: u32 = 0xff; // allows playing with SIMD binarization + +impl BinaryBitmap { + pub fn close(&mut self) -> Result<()> { + if let Some(mut matrix) = self.matrix.as_mut() { + // if (_cache->matrix) { + // auto& matrix = *const_cast(_cache->matrix.get()); + let mut tmp = BitMatrix::new(matrix.width(), matrix.height())?; + + // dilate + SumFilter(matrix, &mut tmp, |sum| { + return u32::from(sum > 0 * SET_V) * SET_V; + }); + // erode + SumFilter(&tmp, &mut matrix, |sum| { + return u32::from(sum == 9 * SET_V) * SET_V; + }); + } + Ok(()) + // _closed = true; + } +} + +fn SumFilter(input: &BitMatrix, output: &mut BitMatrix, func: F) +where + F: Fn(u32) -> u32, +{ + for row in 1..output.height() { + for col in 0..output.width() - 1 { + let in0 = input.getRow(row); //.row(0).begin(); + let in1 = input.getRow(row + 1); //.row(1).begin(); + let in2 = input.getRow(row + 2); //.row(2).begin(); + + let mut sum = 0; + for j in 0..3 { + // for (int j = 0; j < 3; ++j){ + sum += in0.get(j) as u32 + in1.get(j) as u32 + in2.get(j) as u32; + } + + output.set_bool(row, col, func(sum) != 0); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index d6675e2..b312d08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,9 @@ pub mod helpers; mod luma_luma_source; pub use luma_luma_source::*; +mod filtered_image_reader; +pub use filtered_image_reader::*; + #[cfg(feature = "svg_read")] mod svg_luminance_source; #[cfg(feature = "svg_read")] diff --git a/src/luma_luma_source.rs b/src/luma_luma_source.rs index 9b525ee..f81a59b 100644 --- a/src/luma_luma_source.rs +++ b/src/luma_luma_source.rs @@ -2,6 +2,7 @@ use crate::common::Result; use crate::LuminanceSource; /// A simple luma8 source for bytes, supports cropping but not rotation +#[derive(Debug, Clone)] pub struct Luma8LuminanceSource { /// image dimension in form (x,y) dimensions: (u32, u32), @@ -27,6 +28,19 @@ impl LuminanceSource for Luma8LuminanceSource { .collect() } + fn get_column(&self, x: usize) -> Vec { + self.data + .chunks_exact(self.original_dimension.0 as usize) + .skip(self.origin.1 as usize) + .fold(Vec::default(), |mut acc, e| { + acc.push(e[self.origin.0 as usize + x as usize]); + acc + }) + .iter() + .map(|byte| Self::invert_if_should(*byte, self.inverted)) + .collect() + } + fn get_matrix(&self) -> Vec { self.data .iter() @@ -93,6 +107,10 @@ impl LuminanceSource for Luma8LuminanceSource { "This luminance source does not support rotation by 45 degrees.", )) } + + fn get_luma8_point(&self, x: usize, y: usize) -> u8 { + todo!() + } } impl Luma8LuminanceSource { @@ -159,6 +177,20 @@ impl Luma8LuminanceSource { } } + pub fn with_empty_image(width: usize, height: usize) -> Self { + Self { + dimensions: (width as u32, height as u32), + origin: (0, 0), + data: vec![0u8; width * height], + inverted: false, + original_dimension: (width as u32, height as u32), + } + } + + pub fn get_matrix_mut(&mut self) -> &mut Vec { + &mut self.data + } + #[inline(always)] fn invert_if_should(byte: u8, invert: bool) -> u8 { if invert { diff --git a/src/luminance_source.rs b/src/luminance_source.rs index 9c62d2e..54ee32d 100644 --- a/src/luminance_source.rs +++ b/src/luminance_source.rs @@ -48,6 +48,9 @@ pub trait LuminanceSource { */ fn get_row(&self, y: usize) -> Vec; + /// Get a column of of the image + fn get_column(&self, x: usize) -> Vec; + /** * Fetches luminance data for the underlying bitmap. Values should be fetched using: * {@code int luminance = array[y * width + x] & 0xff} @@ -149,6 +152,8 @@ pub trait LuminanceSource { iv } + fn get_luma8_point(&self, x: usize, y: usize) -> u8; + /* @Override public final String toString() { diff --git a/src/multi_format_reader.rs b/src/multi_format_reader.rs index 9583d6c..1c017aa 100644 --- a/src/multi_format_reader.rs +++ b/src/multi_format_reader.rs @@ -17,6 +17,7 @@ use std::collections::{HashMap, HashSet}; use crate::common::Result; +use crate::oned::cpp::ODReader; use crate::qrcode::cpp_port::QrReader; use crate::{ aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader, @@ -194,6 +195,9 @@ impl MultiFormatReader { BarcodeFormat::MAXICODE => { MaxiCodeReader::default().decode_with_hints(image, &self.hints) } + BarcodeFormat::DXFilmEdge => { + ODReader::new(&self.hints).decode_with_hints(image, &self.hints) + } _ => Err(Exceptions::UNSUPPORTED_OPERATION), }; if res.is_ok() { @@ -230,6 +234,9 @@ impl MultiFormatReader { if let Ok(res) = MaxiCodeReader::default().decode_with_hints(image, &self.hints) { return Ok(res); } + if let Ok(res) = ODReader::new(&self.hints).decode_with_hints(image, &self.hints) { + return Ok(res); + } if self.try_harder { if let Ok(res) = self.one_d_reader.decode_with_hints(image, &self.hints) { diff --git a/src/oned/cpp/dxfilm_edge_reader.rs b/src/oned/cpp/dxfilm_edge_reader.rs index 5734149..02294e7 100644 --- a/src/oned/cpp/dxfilm_edge_reader.rs +++ b/src/oned/cpp/dxfilm_edge_reader.rs @@ -71,7 +71,7 @@ fn DistIsBelowThreshold(a: PointI, b: PointI, threshold: PointI) -> bool { } // DX Film Edge clock track found on 35mm films. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(super) struct Clock { hasFrameNr: bool, // = false; // Clock track (thus data track) with frame number (longer version) rowNumber: u32, // = 0, diff --git a/src/oned/cpp/one_d_reader.rs b/src/oned/cpp/one_d_reader.rs index 5ffc20f..8f3a690 100644 --- a/src/oned/cpp/one_d_reader.rs +++ b/src/oned/cpp/one_d_reader.rs @@ -17,7 +17,7 @@ use crate::{Binarizer, DecodeHintType, DecodeHintValue}; use crate::common::{LineOrientation, Quadrilateral, Result}; use super::dxfilm_edge_reader::DXFilmEdgeReader; -use super::row_reader::RowReader; +use super::row_reader::{DecodingState, RowReader}; pub struct ODReader<'a> { reader: DXFilmEdgeReader<'a>, // THIS IS WRONG, SEE BELOW ONLY DOES ONE @@ -47,11 +47,11 @@ impl<'a> ODReader<'_> { isPure: bool, maxSymbols: u32, minLineCount: u32, - returnErrors: bool, + _returnErrors: bool, ) -> Vec { let mut res: Vec> = Vec::new(); - let mut decodingState: Vec<&mut Option> = Vec::new(); + let mut decodingState: Vec> = vec![Some(DecodingState::default()); 1]; // std::vector> decodingState(readers.size()); let mut minLineCount = minLineCount; @@ -120,6 +120,7 @@ impl<'a> ODReader<'_> { checkRows.pop(); isCheckRow = true; if (rowNumber < 0 || rowNumber >= height) { + i += 1; continue; } } @@ -134,6 +135,7 @@ impl<'a> ODReader<'_> { ) { r.as_ref().into() } else { + i += 1; continue; }; // let img = if rotate {let a = image.rotate_counter_clockwise(); &a} else {image}; @@ -182,7 +184,7 @@ impl<'a> ODReader<'_> { let mut next = PatternView::new(&bars); loop { let mut result_hld = readers[r] - .decodePattern(rowNumber as u32, &mut next, decodingState[r]) + .decodePattern(rowNumber as u32, &mut next, &mut decodingState[r]) .ok(); if result_hld.is_some() /*|| (returnErrors && result.is_none())*/ @@ -277,6 +279,7 @@ impl<'a> ODReader<'_> { // make sure we make progress and we start the next try on a bar next.shift(2 - (next.index() % 2)); next.extend(); + if !(tryHarder && next.size() > 0) { break; } @@ -371,29 +374,7 @@ impl<'a> ODReader<'_> { _hints: &DecodingHintDictionary, image: &BinaryBitmap, ) -> Result { - let mut result = Self::DoDecode( - &self.reader, - image, - self.try_harder, - false, - self.is_pure, - 1, - self.min_line_count, - self.return_errors, - ); - - if (result.is_empty() && self.try_rotate) { - result = Self::DoDecode( - &self.reader, - image, - self.try_harder, - true, - self.is_pure, - 1, - self.min_line_count, - self.return_errors, - ); - } + let result = self.decode_with_max_symbols(_hints, image, u32::MAX)?; result.first().cloned().ok_or(Exceptions::NOT_FOUND) // return FirstOrDefault(std::move(result)); diff --git a/src/oned/cpp/row_reader.rs b/src/oned/cpp/row_reader.rs index 69260c3..2dc95ec 100644 --- a/src/oned/cpp/row_reader.rs +++ b/src/oned/cpp/row_reader.rs @@ -41,7 +41,7 @@ type Alphabet = Vec; // // virtual ~DecodingState() = default; // } -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct DecodingState { // DXO pub centerRow: u32, diff --git a/src/planar_yuv_luminance_source.rs b/src/planar_yuv_luminance_source.rs index aff992b..7bdf857 100644 --- a/src/planar_yuv_luminance_source.rs +++ b/src/planar_yuv_luminance_source.rs @@ -256,6 +256,10 @@ impl LuminanceSource for PlanarYUVLuminanceSource { row } + fn get_column(&self, x: usize) -> Vec { + unimplemented!() + } + fn get_matrix(&self) -> Vec { let width = self.get_width(); let height = self.get_height(); @@ -332,4 +336,8 @@ impl LuminanceSource for PlanarYUVLuminanceSource { fn invert(&mut self) { self.invert = !self.invert; } + + fn get_luma8_point(&self, x: usize, y: usize) -> u8 { + unimplemented!() + } } diff --git a/src/rgb_luminance_source.rs b/src/rgb_luminance_source.rs index 144350a..aa95341 100644 --- a/src/rgb_luminance_source.rs +++ b/src/rgb_luminance_source.rs @@ -58,6 +58,10 @@ impl LuminanceSource for RGBLuminanceSource { row } + fn get_column(&self, x: usize) -> Vec { + unimplemented!() + } + fn get_matrix(&self) -> Vec { let width = self.get_width(); let height = self.get_height(); @@ -127,6 +131,14 @@ impl LuminanceSource for RGBLuminanceSource { fn invert(&mut self) { self.invert = !self.invert; } + + fn get_luma8_point(&self, x: usize, y: usize) -> u8 { + let width = self.get_width(); + let row_offset = (y + self.top) * self.dataWidth + self.left; + let col_offset = (x + self.left); + + self.luminances[row_offset + col_offset] + } } impl RGBLuminanceSource { diff --git a/src/svg_luminance_source.rs b/src/svg_luminance_source.rs index dcd7ab8..02dd03f 100644 --- a/src/svg_luminance_source.rs +++ b/src/svg_luminance_source.rs @@ -11,6 +11,10 @@ impl LuminanceSource for SVGLuminanceSource { self.0.get_row(y) } + fn get_column(&self, x: usize) -> Vec { + self.0.get_column(x) + } + fn get_matrix(&self) -> Vec { self.0.get_matrix() } @@ -46,6 +50,10 @@ impl LuminanceSource for SVGLuminanceSource { fn rotate_counter_clockwise_45(&self) -> Result { self.0.rotate_counter_clockwise_45().map(Self) } + + fn get_luma8_point(&self, x: usize, y: usize) -> u8 { + self.0.get_luma8_point(x, y) + } } impl SVGLuminanceSource { diff --git a/test_resources/blackbox/dxfilmedge-1/1.jpg b/test_resources/blackbox/dxfilmedge-1/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9f342117169c4b693bff2d6744e6139835fd2c3d GIT binary patch literal 25235 zcmc$_by!r<^EiA_un>_B1(jH&yGxK-x|Z%Gmy!-8l#oWcK{}R@S{eivkQSB&DUn4w zB$W{OT|eJ=-{*ba=Xw8p@7c4QJ$LS$IWu$S%*?r5f4lwxJXBVIDgbzR0DuP;;Cdbp z{~_={g#>Jo0_0#J1!#ar05w=?0A_#zcmfs%uq6UK2iX6+iU2S`5Rd}ve|^IM6~Gu! z2IRqN47T3`2*3<12(Xn6gaB{AnhkUSmfa@NB#Uun1MX2HrDZ!|y z;#qU$uzkJRNpAjg$=xw{Jof!?jbt567~D%P68%ei=zkiWR*>Ml-8w?HeI zOQn(aJ=TXE*J=I_cd1BVNK*Zm<7hp11%folG`-?$8MECQ?yRcM zyB5E=D0{Zf_NtCY`5$-Wbke^owECS!rr0sExa!?X!FS z!v9KsSE|%y-_L$xF>b~~zwgC`RID`rV2I;4?BSc~HwPO{g9pE7G&=K=dPX{HxrCoZ zW1MMoT{BpUj~VydH(z(2ib`?v&z`Wo(HE@x<>7lGp{I6Ky3ih|Z)|L*IPbdY`}o<$ z0z5=hITq!kQ@dRkHkH`M_GMp9D?x)Zi+#6L5y7Kcv7h|zTWXtB0I3<; zN@BO(etPkq0g7L6hv>Aqxm_=_L8RGDsqe)dI@(ryyt`0U&F4xLF;;i(7q!RWF%^;B zA}8_>)AHTo>tz+@l<5X8wEtLT`-$Te# zhrV6~(%|Q}hre1d$@YKvf*;*H@QfRxaA*w>umU$}YfCyr-L#VEN+6jIAF~AjP2#j7 z0LoJWd??Z$;F<6X;Zpc|r~kg^&~vz)zp(EfAMO(w{?})4;5pvmarYPHNG*VdBP{M2 zflXr?J7Dz`9~h!$kd&OVjvlaJQ@cA<-Vc!TtZv~wW0m8uupUz9@VF~BG(2iC2Z4|? zy|aI$MmXbSVqzu^xY?`2`gwqlu1OUWAaK9Hp{?&enpNJ+5m_6>LN79K_bFbC#qCcE zgH*!N47Bc_nN8rp$6Kf8JKChlaUT0;h|iAot5f$yK>*;n1@F7xk6Iul#Zd*g zuT}=^62YG>PUntU@9K~|L_Ujq#t;H|v%|n+*~b@r54d-Whw9T}OG~rGTEXNS$-i^2 z5AWlJ;2f=sio!_lQRt2dMA^PMBcT4YjRpV$K(n8RW%`&;O@5eyg)%NMceys}5t+sP z<*mhc5jtddtkL;x!?kWAjr%Jv01)^l^&PXOLJv3hJ}ozOI~_=q%;)>6G@qwD?=rIi zI-~^F!|AbaL~1`$gBJM3gUFX=ZRJVy@|r!x`K5xK@LK?p-y#GJkdoa%At5^SM82V* z6~+U?@7v$r_Qz6Dap=e4GRaiY_i3s9svgQZStMo+XOz@BTK0bc0G@|8UFP-X$jp#I zOj9OJ!kk1REsl?FvEljUkleXPoz#BzSm80X+sA)K-;Y}m17|}Rl^wsXcBO~9xUdq5 z3aohHy#U%fvx9|rG`JiMJ~`q-|G`RR)J;N335W#f3;i*fFJXerD$xVu8&5w1RB4zG ztgV1of(3Tq&VSINxU8Pt4dwv?k%b-}N@Xqf7CO^ z;^6@}C>RSQi#l_A7Om%Z;iF-G6OX#se~G%>!#cYyq0-uuUw z##zxiiL8P`pIw~>IkI7yDc&gL@eO8r2zg!Ka+`SK31HPX>f}q_@`DA{k zgc@cq@PY6BUeV-087P_%yqm`XsdXxh5Q&HshwbOFo1ix3aRzH9CP8d(fo#JMTl>aq z6^yTmr$qgk7C1IVRde*_(Ur)T|FqjU^baNnv=5uLBbX&xG9akR;2V6-?FOp=DM+y9 zXMp(S0s=SUS#{S4gW=zCTCsdVpqSQ^tjbVl^Ccg#{pE%q{}enKz)uFX@+P3>Prt2~ zL1Yc>E8y8S+i3-E6b@TKo+f@bDG|kf{y=QUCE@!yA^2V}a8H%`#)E4h04WbwE7jZ- z8H_vN`S=f7`Dy0;j4wIPo*_bBQ4f_sDnL4jcnH29Gc+=)OcwFtZci}v5bK00@Q_vZAs|4f0pk=Iz8{^*fSV+b`3S@_4c&M~ z^n!qcH2i)+j?m{@cb?smy_NYOtuoPtlbBxaOS5eW-7zQ=iHsM@!Q^D}9BxmC;GP?x zllq)V)*+ZHzG}dsANtq?GvF|^?#g5NLXVjJ1sOnwKjHE4gQkx61~}Cergwn7@TF&S zyPu_*M1r-EV^-U%{+l%KhcQf%Kst2(oi5uwmqic~j}8Cnt&Fzqr09VZwUvf1&2|#= zP8OQf5|}%hfL!A4$c+yryC@J)J0npJ_`G;>xB?M>cQENibzlD>54Bd!`VG=x{CvuV z&(B%gDnPg!Md?hkaGJ&)w*~6~_d0lUDbbB$;O4D;zlrDX44yNgWNE|Ht3Lw(>rFZ4 z8-UHDz)llg>y%fRG7I(?3qXf=KT%n3j!9l1ih#2X{@en%al`s8DMYwq3EbB<^obrQ z(5CxSe-=VfCOW;IiC4Yp!CM$ZxM4=`NPzirb{ag2@%3|}GG-n5Jdsug{~0T}+mv6< z#a*7`{z;8}=Qayj&VyuV|% z=YJA;bb8;k^OGNkvw4k1N>4+Z>8nH9>?6me`Xt=9%O^#4k!#9bubWT)av_B~`W{~c zRPo3>*GZOv6-lzL^-pONCy@st}|*;sQkl+11RqsDU2zZV}C@RpCLyK*g^Z4ljtw;)Vc{5_sg96q)8_WT(B44cslxzAANjH?kKX|u@Rcu*vQ9nUOiTS?@u}@fip@vHzkd16T0ml4Mg7D_yWTEq5Zx+z zbksWM+vK|#>qO_Gj+@wVN1X|8`PQeE_8dRXHSnar2FU8GkXYRD%FDKaxo<0fME%YL zw<7zHUGGv|g=6A1dbcdPr*J0?slV83%Ke(>jm{eUGyMwbuQ>1f{T6oHcHLZ^U*EMX zByu?v&-5sa#tewFD866(>9~1Jbg5&fBlSIYxc{x^$ty?4^FXsa4ddecO7HeHSjglN zx6pe>jmsbIw)M8wxd2al__kkee>GR`#Fz1*V}-~5IclE2eI+HGOm$Kgmw)XV4$P0D zH!b5_#yEF#kxo1|y^Ya!I<_qKPe)7h=*K#-E0uneS@aU=M+zoXgzO|qM5@M5WAY)x5y-7@9PSaP&TSu)D8?AY4HB*0@wWiT>l9h{}VLccnJ3G{OgzDxiJKV@t1eBZ(R+IaS!p*Avvk&G6ClT<*eyMQLv$p-+IIXX3ZR0M~ zIj8O&KI3WXbL-XFe%{SLe#1utBt?fYX?cN_zTfxWw0aFtRrvW`)VzD$v^-FV!L4uY z_KNS0C!cuWjva?K=id|V=2Er2=gN&U;jZ>Q`sHg^AINboetNJpv9?-c{(xIgVy5#i zCct;a)Ghpof>TB7rPf38`*&`H_;WVOGmR^NHTE%R zGvv=O!s-%pFlfIMYSM=ZLcdJ!0vb%DToIA}{Z+FFbsztR8)U<82~n!?GDa1=VlE%a zqw^s6oTMDbJCv3_t$6%*B)-~~+2~;E?mL#0-Twk2qiJZVBi2hI^l80eYlJ89;l)Eq zfynCC8nhx%Fw%SEj5 z;7uA*UK^`JuX)$~eOA18SI0FJxurTX4m*qwR=Nt`Q`Pygl-8utsn0LR{uxNwTgScj z_hl*Vil)1i@ZH8rjKfxjn^a$Qre(#~l;*v65oy)p$;^*ge?7mtf zx7?XD9+#W;_?-TY_xAAkQjWjaS)$QsXWnfy*B~z1W>em!H1?cdGwXe0!p50DvYIie zjqQ$f{BG6hiAP(a-r0Ym+sJ5kyvC)%0E5~Wph}K1GXt9VAAy_yUs`*+{*W2CHA9i${AWt4q-(>!RhoMt&FG zey#rWfq`+^zFyNJ{*sR+uHseCIs$%W#Rq9TNn6}JIu#GNJ+iy8@^g20;N2yC9`lLo z$old5n`=P23-j~fb#0!snz~v`S?BjW;ik^NquUa#KI2(tHTuH=iesxMg1Ey=`UMI4 zUHI@)`;ocvu%!R;Fa{$bJm zhctCW2@98o4T!b%168sZko{u28F;2Tl3U9etyEqXXcnx^}EQtRFW7!$f>5YwM*DpovN{rCu2WOkFm0CO?vfj1n5nNIm{Rxt%_k4GvU5Rt=RL5v~V*#5UYY4OdsD)C$|W{hJ9Q1LcJ(^(?k`QfhU6*d z(y(p!K718fv*}D4{?fXcO(Hud`MRz+N&MqV_HcE^k-KY&8+kHk;mib^Zp8!Ykjf)? zwHx_IfufV+J7pi26V+Opd#cMALa<+xWkW_PM<2KJRT<$^NSfZ26(ANH;ZW!GLc9fz zkCX8~F1tL-T#t6va$wRu_5zY#tk~LQG|_Mp%D(u8ea;X<#xv$QO!;mSo!aK9d_!|& z%#o0JD2zO!36=PcSo(>+dZ7AM#?kp}Gx{Kfvp3)O+S{e7XZN?~4pwG6&7{rkLd?GB zO409Sw~Nw_bmr*~Zyqo4=S^4{(-fQBsdGlF?BNzZa0?(tsKL9`sYQOkH z;09>432qVK-@wEFPY&<~m=(k)Abt3Zflro03HV?mmjuj)*3z?T%mYVQ)iIj5g zZp{Ck8U&-PHrn(yDpxmeuJ5=D=k>0{c_%mL7UtodWf5;#$i!5-f(LRB;7Og zHXe6pR;T%$L@0$(PH1zz`mU7p{y@ZTPL-9H7KJ{bW1cY#B>gMbL=fpU<6*R6FUX;M z7_r^w%*26k{MC&RuE`Rs7G>~6-!_sCWq2>dV^2OVb8dV1!t&?ZEooxz=; zTy@vb2#3r{h1c(LhDMpznuDBWJrLSUZ26*9+>M$Cx)&68M=6({#Z%$BEc)JzW`-zQ z_dgP-NPjKQd0bn0p4q`%W~x%|#Z^39?x9sSg%G4oo+>HkLTEEX6IX}R1gQi${?j8^ zUaqE$(9tRj7t9>BL*%ZYG~AOSi=V7&yCpOJJ^s@7HSY++@+368VW!Z#-%|h0OzFes z9md0%QpPnAIY%K#Ha&N6{M9J02M0SRu531yLhlF`HumYkxmD-c$fs+-E2^cRG>u;n zBh))(sxqgl5EMF7LYa?CZunawp%*`A>tW7ATqNzMxzoZF)qT)g8E>ZZX zvy-qN8SEa=b~;iZa10@;uyUV%tJThu+R#3$%#O?sXw2)@ia8{SUZo%oX-+_F$gPcp z8hT<4Nc6;%hdM&2H7a=bKULUVY(B5;(aX|jbL~lIy|jHN>dnvQpHuNVd8#ur^>CP| z3(8Z8N)Ug(tk*oX-@1D_#!vO9e}q?M+r(t3Ua+9~EcKbBV`TbFkaD3dETu|Nso#p( zl|ub2>Z0^DIsikJzT&WnF^QSsNnJ9H5*g9aZlAIAxbK8*a+&VK9q4YFIyU}Qd?J0B z8BRqJRaS*0(|qH?=~(wCGqqHoe587@H*&C zm=zbG7+#&X`KR^Z4m4m^j#H50pwGrpQ1wJ6qrO2SRUiD zBbw^!%}<>ZL*^`*QK zD$2d#K)GRV^Kq$`{!gBTe!?yCk;1o1YkY&3?9^RZ?iu<4_ev;(JWHfgR9jt@V#QQ- z=bEnpWs9r)h*i_{YJU@9*5dg9BK(29dq;gQFOfkdtZLt@qOy>cscaMaj>9hHcdMtO zo;T7py0Qi41k!zNeWW|(d@AH%8^4jnvq_bne|XPaVnj-BgqV?{Ge63lRn_$ioNJw_ z|L+N!g;Qxe0pG%H{9Q z3;G=%E88q7b7NVVV@zI6GFSZGYHd=+l@=>ER19-$wreS7OZrXPTiAW&5P_aWksea!jjFTH9Mc+Z2P6|VR)N3!jB!tnF==)e-O==ZlReLywINZo7+FE3} zO-A&O!-|eJN^T`>xR6${{2~%lRykLOryUy%>&K+?A}_*7XkWF(P|((zx)e=XX@4(9 za8@VD@a?v;@+#g+tP-ROkYKCQTmBKl$-SxA)6lUNHsmY(zO_1HK&PUi*^7KrOmHn_ z)e%?doiqHHoqs!B+fE*JbT8As5=w5^apz+{Jj6%7E+(eRVNukH+oI1@ayQ14+Xv1T zhSlw}_}@ zvDYq{32WGiz--7?3NHJU7@bhqq-YT4x#va{i?sn1^`rd3R&`w z=~1R7={$zcT!V(mCJ=f_^6Xm_9lHINlyN7={ah79x6EQ{x@`H91YdWjSyC2LL>|Z7 zq7+kKeg8Mcj6fWc!Z;XOIw#gmZN!F}^g+;1>^B%X--GFu!je0uV%0xs_e{zeSFP6< z?`k_t=3t!A5P2|ujKa|0jo!PIlL6h zlT{g!%9S%wDE2A@G;qnk&!DJXjc&@e>zYDi-a`r|*sLi5FhP z#EJ_dN!1h4GBLf5Xn`&9%!|0S&Xrc~mKZvGRp+}?imc-N272<5&v-gl1uO%GjPD{d zH0B&O`HqivtTkuteZwzixiuSZ>XLfW z@jXH5W?XI|S37)zWXR2F41TOTiAK^IV0x_EKIumO6me-UPLs`e_q{g1At%dGTqtFg zNB2ALsY4j02zf*sPq-b?EUR|Xu<%{=kUm(3Hp3V7B(`YpP=^F~m{M{RG(&nXl|p4x zWiT{6M^!g)C^et;cV=eES&zo<)RKECYlOltiqZS_@Ru~IZsp98tNhnM-is8gzL-vF zQ{^C56_Q)8ntFI=g&TSc*?-MKj1M0cSv>Ejb@GK^%_ETr-sIG3w#s3;3CR&3fj_*9 ztcuOO+RDCBs&dtdLk6Rs_6CX1wSVWtayyWhE1pJ{WcsOA&D8CzOp87!C}h)aM{U(r z>0fcQs&Ey)SE?+B85HUzO&W$9GOG2Z^PU#vQhYZL|9M0*q+Uo6vBP%Y4&e&@wX16a zBi<`WVYghS)hC_cKyv#zyML)nO&}rj^cQ26tEU?ZueRk8EVsnwXBOqMhJ|mj>*^|G zu)G!kV~e_UVO_5z?V;~P%KViyp~e172a6x}%6#NlHI-e4#o5U?sRa4JWXtkUl4@@t`E93aR;ZoUDAy(ObWYoT5u~@Inpw`m(B~nI_Ou2w# z%eeO=)`9EHCWJ1+=)^aGfshpXw!c98>n^<0^6EV2g7C?@4qcq`U_t6>#JmTwVHpfd zpUbKsJ)oIAnKoahJ95G(iU%cQ#EBrbGItc(njuSBPRc_3YIG(S?9mT6|;~i?v;Np3&4thf3jD; z^a_k4)Gyv&RQT*j1SS7E4Uj;uQ3sd|1odvNNyxtc$t4!z5HBk*Ovj>ysV{1x99&~e zZXyxqg5Fji-p`wzG>)isl{iDr?4(_e2L&N=u4)?9uL1qJ(jernP8Sx8EyBzZxzE6k z2=E6^u%p5^B?ytrVmwzGISqDrT~;XAn1gr0AEN)c1I8mb-+07-M<-E#HOTq`St4;@ z!W&&1xXQ@ecaePXSic22;%2rgp-A;dX{0vD^WeVuqQpz8Kj7HW7ey=|W(HA=k z+!WI5mTn?7ooHO`6R)@>66sYdHXO^}%9q%SN=MK^pV8l|Ex#N=1bNVAE%eR*`=E!N zYOVUBDYe1MVSpN3eYhzWmsHktcUqBk$~bguR@~w$Qo3M45jy4e#>s~-b=EnRLnF}} zoyIbbFT*sRxcXa;gp#`sOo;TixPu`fmr0BJ&%@+DIV@TdrCO6-B=|=lJDCo5--)V# zjY*CE+Sq8mv!Au%EKXERIjDJ(I_P2dLpiFa5e!oJgCL5tN&}Fjh?Et+)T zC`lYL^h`CCPlMX9Bo|M5l8t!gD9TLBV4EgT-H+mOa+sUrauP-lYO}ZZtrMXv!w=;s zAwsH2Ok=~l5eyvWR^247VPfi}YlZ)beKSXG>mE-#f)TED*WK`}$zm3nsecGv`0lRF zW9yN2C9|uCUUbq_G-F-2piGI-`G*`N_#l0Js!7bhWgf~ohcQT`&bt4s}=#(ONU;i8IsXLTu=iSo?h=P?t z2A4_ax2M`@Rua@aL^qXr&>cahnHgSh2*&IGr19TESsg(IIlkBsi&zzl3#kl2y6TM; zsFxfL@J@O?mZo|nT_NKd!j!1~g}iwLBzHJ%*17fuE6LA}NGp@fP%I>$3&CZ+GC!QE zv1?CvTW_ooOTPVdYj)DVc5*;#@^yO(TQPH+VBG98uOOZ1V!{y!R}vN$zC=3NMfK?D z%e<7~ET1ZfodIfPoiQu!T-Q_FS0YErVTP{d5|pVRT^$CKE$mC7A3f<5p^rz*gCkeS zx1Q$eRK{tG#w9H%3Js>j9>VFis+)iI$o4${8m)X-VX33jXh1WWQZWAc_5 z6ig+>yTu~=3Sk9}Mmmbhn>?_T=^;2>K_VvJ2!Skl8XpM}3o?jgP)fYuQXd4zIfQ~z zMcrW_kkOlGj&!RqKoqq>`9Kc+Q{gl&gdi1{4gZDFZ5fFH=+pqHbW<`CrG}tcaj_MP zOJycC_AievNKy<>{2s;(Goy$si#fRlaL+6wR!wgHo!2hFjR9q%`9H0X2)LNlg z@3E0k{u?V+E7CDZy8HhUEhw#Ija`SF+YOsy>A5c}V?SuHPJ^pgOaWUSS6CDW$Ep_= zqGEUrG z)A^@_`QHC`>Sr+?Wml1Fr**c{n*Xa{0crn`QQUb6EvoD05R^fZ?lKJq;l6zxupO`- zfU{V6t9pZ9iq_sN*4~P?HY!e%j309o2@lA=c#+hJf;ze3oWtlvGUBdr~Pe_C$UH)v&o2!S!rsayl3XdMTJNOIksX(D!lRH1()Otl1LGY?{lh6B|Y z{J@2?JUt*Y#r0)P>_MHVdV!z9mRR!UY+sSB&C;X6>isU(hyi}7SWeRVpoo6U6SeiG zO!SKiIj0Qf{&LHT#J+3by`@7#g5u?eXo6`Xg?huv|15jk|7j@bjf(gy%7`_B7LEV2 z2Ja)W=zThC_gbdIvV{>Zuv4PwJZ7i@1EKNahvh{>q_?t0t71Flahq(56K|7*Ekxc+Q$XBrC3P57z-o%AAmjDJK}Cz`$?_GNWelF@?~ zG&ypImq8puG0Pj-hI6)Al{X5!OEBH0 z%scO%X0SNL3eiO7EmsJf6M{UcD5~XB`NyQ_q`xU*ch|&})>smt>x`v#S|GmR`Z;O- z$6~1Y&@E21XVPPHQdL(h?5LX*!5qDt&PMk4xn7>k^J}2)nQj{Mpq1Sjjqo62LL{hX zakgUQx$aLv72aUUcoQq3sv-0}@uvqJ%bx@4{s5S{_Ie;tBbfpX@13 z7K(DIkVy{>mL5l}TB+=plc8tC?yfV4#OsuS;fut4Kp`2XPHB}X4qk{Gj6RR*e4WN6 zL$45>LWw?GA0>2qzBCm9J7(^8H|XkZ+IM$N_T*(z&QX+28fjzjRa$5Hkb#@urOtKE zQGj$=q@85KF65@vOkN6ZP8w_X@oyO}97vP2sOnF_4%LUx$RiH1ETB-8Ii-r&%K!1` ziep$~-`(&Be8M2nPiYOF4_!0;peXuPLukn!eME($!wL$dH0l-WRi0aPYSCJzJ99@k zm@A#eSOpeT&mO%f^5cTBEbEkVG8B|^TA8GBUjr`&{R}p(6jpW0RE=Y+OXo5Kz8?(D=*VJsFc#v z*hKZAM2*iz9!%N>C)`#B|FFvr27OUvC)H4;Q}xU-W=T+5=LYqA$`dunw8fYa6*G|Y z9do}}`X)<;w`aL{?xf-~iN5A&52wVSzX>CX{3N%bE({AyyDiti(GOu(<8|yjQ_@-_ z1C%M@M;>!nG5?kvTjI}J&TBwnP!vQJxyh2f!IbEyn{MbX3Kfg@9(UL>j<%+|Z8r8K zVxibIOULQ&9ZKlj&s|*y3W@-zl}W}ainDI%%b?u}f=*nrXq=2r{84f)3|v!8Tasvm zWYJ-cO8wUjvAD!QM)#CJXVd!qr-l&dtY#tuPW$R-Yd4ol(1X6N*u=kESph{yd$Z!< zEGf{10VwE@Jg$^^T%o}_Rg@#&d@EZ=5}V49%8Em3Be%(dKx)jh^hce+o$qPqTqq~H z)N&488}#E_t#j&;@hOe)ScMEr`80^9f>)wKLuwG*oKv61!Nk;yXI&USx;Vy4R8^VnO5ju2=yL``Vsd&gSxt@%{4WzlBFlxlk5szo$8|OxM7TdC+nUgNG_M zU13R=^4g-2vyVp$(ePH$E*%sl}CIZC};8@nG$|Ff%Yt)2J!i~OloMhS9s>g59UH@ z14-2jSU_5ej)ZfeN9eS8lZBB#p}|lr-((Y!Lh{p zV(R&Rx^XYdLdu#RfEGTG*-SyK{SYbzJyIom@+F-EHG$HGGf?r%JOA%;^ z;$)$q@vs#mJV+SwA{;7J7)un5Sf%HEFcVc7CU)Y06K88JifsL>ybz;^PJEM4r|@p+ z#&?xBj(*Pj{@$y`wX)+RlLJlG9wdj6V!Gh7XNsUx3?eEJ`X}oIFqZ_co9^SvlUwK_ z?HaO@VoLfMn+Rp@N|7e@YtOL5vOY$;qm~q;(dvT?zT*c@o8Pia)yEycREb{UOaS|! za$=#OzWWwtpK&nq45kzxDb8X|Qs@0G{gn4fcTRm%>5tX6T4iq0$5r)3TP&+&b$&|g zAhI>n9934(42@OAzg1Ye-}58Icx{T519h5Slb{>Vakf7Eh$*LjE%t-)DXGT5aA!rr zqi=Bvpl#mK7-9+mw^vWH(fUxG;<&0DIfR%xr=mu8Y;#SL{xH+o`XD`Yo+$x!hw&;b z?GnZdvJzZZ3pUMnXD7XK@Q++(10j?u`)N#x50c(%J?|!5D3=E3;x-;oYmo6(8#X z@0#D~d*9+42E5P#CE_LQySxjv<_>Wy(HDwteV2vJR#y`Xyg~MR)LjkgH>rM)ErU1S zZF8?80$fyZ?`;gCduGK^mx{|7zUtyU^WlH*eG+{7=KR z5jxE4Hd(aV{4Fn4!8o>QSVH(wQ0l+Q;|Z^qBcfrM#CzY{q$iB!R+F3(YAOgCP_)7v!MupI`CxwQ+S%O4Yl{P%Jr~ zSK%zRE4Lxd(sg9}4ri}o??@#kME=n&Pn#)eisdpNehHlD>oykl%OTFC0(+YA{x@*%uymsS3J+b@n+uGBC1=7W*S zvhH2ZBmW&cL1Rk2Rpx8^J@Dod| zPy{1%8`;go#!{t!ZgNcijy+Q0P$R6W;ZIpUZBE~Bm0IJa*2`}O!-J%?lDkerPhQf3WLf+fj zB4;@g*!Q@Kr&=#!8ufLc?o7Gb4VC)HTg88z&8#x#Ql7`QWTKaUU7~kd_-wR}>|CLd zixEiYI-T#^KSyD&?`g{|yjvY9m_~{Rz&^TozcbW-lheWNHb41?98qZXT#ioMLycYD zR;fyH!TVTp!1d*V_f$u}=FqsAMGIP?N`ZBPt?>_CBlj!prlMx!hChP7ic>tj`&aMR zSsPxVY(^7ALqUzu)m&;SP8?y>5;vWiYZWV(ZF(o1D$P&Qn4j99SlK$Wq49`BFEtAR z(evpaY1^t;QcHvOyjmFO`CSS62QZ(?IVw+PW;0lW4bOypjI&<0sYipWTUD%+4aP_N zw`t3~fTdnPO0f0;nqP%RW5~)|m^I#YaYm5ZOfM(-EaIo>2irVlg)s)zniSMHYvg%U zkyDGJ2BP%AaCz37Fh;#G1}_>#X44MFzjL49kfD--EsLx_zE%R8^Vv&nXB*CW>?%bs1ZW*dQX~181nF6wDV+#&Z|6z*tv8o z<_a+cO{nNwH*w6xv>rs2$>5ueNICF%5x*;1FfuxLC+|ELlgp8!sz+?b(1s9?zr=v& zfs5i%mly&Whq?nle$N>hEA^kbD=jQ7ajPl|Xp7C49=xe+6#_EiEdJ?H3 z^73_wI%|w6-fKb13bTAw+xm8QB}!Pf*v?p8DvEYT17$xL)TQdMG!%P)?*b^X4Qyj+ zwDluGdtU2e!u~2te&;0@I6p4$s~5nK>+2W!XrM8nzspPS?&_$MN>ZpM>NKJ^k3}M| zWg}K;zV==MmI9TV;u4T*9R*6NBHx~Yk|ez2hC9M0$kNtYtyf3pfmy$Op?b?YwLMoX z@USrLC@m9Hq8t8XNEHuK+eaH!m$a#rh+mX(ROgejm7E`Q#A4bs{9Ux1Y2%)tBXxMr zijGJ|j6>6%6?BLY;SjblhW3Gy%(cw5s5Jt58W+`9yJOEvqI%AM!^Xy1X7wpkoK-wH z^>pnMy){^)igu%FcsCl@F2amCQ998rw4PjF_$4J5B0&Ek)y1gzH`F|kp?aP?->Yhj zPFy62OaL>>5#&axJ<6P3Porvx=UFag-^G3uK_=AUr!tj#s?EbyRWPCp_)3=>r}IiD&5UA1zueOUkA z;O&0A;2&O%d)r-Fwv(zt#cO=0sAommqOB1v@!x-Tlb}HswWRdtPMv^LlW9mk@GSPt zMB_dkTxlINdl_%>p0#3;);|v@|0+`tRBY8*9dOF%S@h~5$}3dqts1uK2A6owT{#rQ z38iA-_5K26@82v}G|Jhz8V*P!hVNS;1=;R@wW#Gb%v^PXYoXZ&N{H#}zzi~z*3(kuFjdVjy zpVWjoPpcu!qTYYE_J(bw49xtEZ~v3SN8-Ibd;E__Yc<-mf-A;h)g!ioMp>0FsfDrc z4HMYgah&C0{XU}l%Z(helM!@CaMBVIS{YM)om?PMuZttA@Lc zb@OiDw=UAVSGFe6+PTj>$T6K6qEP{LC2+60s;HQ;8OeZwD`T-3O7>5wagT(oqKU+G z52EO?ycrdO>EYRD( zIprJ6s-|h>!CVD{YsGnowe!BMs%eZ|5}B)vn|7W;AbKmoBS1Ix<|u702dLhWn%qda zL}QBL$R@+Q^7j%Jl$zESHkX~7j2dOH(qBjbo2@`^@m6nLWW;dUnBO%vL8OZM9`0NwQJ5xhA^modW2}~c zpqXlI46DN#=A&?-vxb-l)mz2fNPg~OzVfpYi&5%u9?Tntbm-+GBmbGQO%Q9050+Ou z2+?mW-3%AZWm7PgZsOIss3>9Iq#l;akHKVkGu1dMEl;W^8a_K7uX-b^X?n57yH{DS zosx(sf^=;YMI*+BOZpkf8}z;Z^uj5T4msocKVkH~-&%T9_ajT&l5M}WDp*PvmFPSa{dy(CK=F;XG5LETL*VwShiZx-dFW+i5YOH949ryQr+P!5w z2Iom}gc+I&^=cz@JyBWJw`vfh<<9As;xu;r)Rkn@HPtlE-=|Z!2GrJaDfX{{%2D6S z%gYoJCyV_m8KYf~M^Dw>fogt!;uf!ZY*2Q|TpH;$n0#;MK41P&s zjy)@r_X(>IqdL4{dzCgieY)r$sg0)omDbys6`U5H8BAXZi6Dz7>BE~5T7 zN_<6n!=d(@&y(r!7U^rCaQm~o3vMwrB|U}~*JWhXi_y<7gvAIE*u!Qj)*L1+UrBaw znpVYfYCNoEA%`s28l1W&_p@~}7}}PUW%ElSjF8&Af{(}-W0@D_X-M>Z(tQp3XV|1- zj5Ta3Gc#*-AJu&o%I%I&$2@GfymtyIgc`#}xF~AxPdida zha;DosouRZuBi7PmENwewgZc$Z$;gy`ZO;Q`K4Qpq1o6B ztA$Y=d#^#Cnzu9g%)2y4HP5~qRvlChrns*)T60L3Y(YY8XUDF)YLiulDGj#LI3C_} zRai8R+80GWe1%9V8gNCkhKEH7#Q0#ae2e3#`vZY=F?Ay83AodW5@B$eFvhfY$DX5D zH8v(0R5AYAe5aR`Yy*2+2}32kTOE34tWhGRMW`tG`=&-UC3+L?t?es%p0yqVV5%$a zQq)M=v!8v_U|1MtwI;h_R)aGAQm%jYuUAHF=lV zJ*l(^OigfKbGTyUICpQtlCT%0L)kQowJYkWGr6_&SnJRbGV<(3-QsEr%MR`6YKAKF zO`e_Ra3p7ICq)IU=&O45V3gLyoHo4h_m<6@6N?f36N4V*KmH0Vs$b!FMARiuM;P|{ zZ|hqsbxa*Ql8jE-@_O?~Gb9)XQ1To&X{T=(%Jc0G>Vke`6_f0i@wrqE%=2$Mp+lbl zG()yNXv`d0Ta0g`@n@VcsMS~J64SE<){0B41XsPz(aMKewNbdMPk4@cH$HyLDv0Y)IaR(q ze;-=2Sf-$z=bf+5ln-;lB8=h`!-|KncZW6u=H6ce^gY{HPVEyue+n`cSUJM*0ezM! zVoCy%wpb=46KV(EvvJR4Lt!=dE2S@cN>oD2$Euhm3h$~pHDuoXQg;@hBU7`SZm!1R zt4B45UKKI?BeP6x?)9?gRoA?(1Pj5g6<<3`zQBWIg`3OJqj%$WBSa(2?^=>WHv3qO|UD$ zPq6!h?Q@qTVPx`;@1T(koh55}iMd+AMJQ!85>a|1Okj7-vVp;|9t(TVg~5J1=b6F&2zO&V zQO_kxf}imBxBYhM#`t@^S8_H6ujj!$1Yu!*a|OwVA1Nn_4n9M#s-ubKRCNY0{c@MVi}r63ZBQ!MAhU#mbA-zl~t<@)s%7d4%-?&`cos+MTIFQui@m0Q(y zYFAZM(6G&@(6 ze5fiZ=*+2o>&w0uOMCF{pgV2$X5o4dRkEIlQwdX1=A zb+Y%P^k!tCE$$a0DD&YSm%l1CI8!zGJ-5e-R|8k*G!c}`U-c*G)+^@WZ29!1vnLl+ z@W9cf$hHHnu1GH~Gy+UgZbVo{Pxci~h8cBG|B4Y{uBybkt7#?Yn^ehZq{hubKlP)QSVpAooO85S5^<8hVJFyf?|rVq=>5Ih%qx$X z*Me1cwQglJ^g8WD9_Zf#l@ccT!3})}@Ml-Jr$~eeh<^Hxl>#YODV3_XFx;9FdZp zx->bGKW{O&Scc%$a{5UYe}xaIUN7a~8FQNX_ISb~B}*sC9+*67u2<(wIgayjQ)HTWy)g$pqgc4dp5?%q<1OLdUr0%eMnU&{h> ziqG{%urD&}ejPf}p{5Kg?g8Qhqb|d$TNYqdKKOqYb28N@pN)EOT^+f!m_2P%h5u(M zw9!kgBQ#g)FIj|SAV1Mcmd9SQHYiVm&N#?*f&LXF4OV3iNt*G61RXV$3FE+r+NOi+ z7x4t3-wUAgrX;c?YGq&fVg4Cm8!5_Y2Fm)UYWoUrd>sU&OFmhx_KyV(7j?^Nw`}jR zeT{DEZW!|%Si8V`L*ssaV%p>M={RDu_T2ge8r*qz%7`IS6 zq>xLIoo^gG9Th&p0K3~ZNrIgWzT-{aWJWe)6=f4#D(!1&?fBA!CK5K$)zq#vn-jgY zuvUv1mqh#&&a>eJ)|42_&*Qb&k6W%XKK-}1R+$DzGp9-8j*C;{N%hL{--B9Urz8zls)eE=IOrU8qQO0| zeh!wJ5X^uJGgH;ZTduP4zV4;Um+j>-apY=M8BQst1x{^(nP@iyWzlH6HlZCeO@h%S z(e78yp?FEFitdF@-G41kd?Rtx3fHxld>-^g~z0pmw1!!H*qMa3{W+4qy9w#o>NK(L!0j zV97P8@CdI0OQBhB($b?gujz+q!rp!CkhKO^7I9r%PGmp+c%u`+Cnqs=J=G0(RSF5U z_i^=>60t6VfY9^sgP`^(C#}$P0|ul40YLQRk^n00SYqRV3%&mDEjfU}|7j7Fu?k$j zscg^1tXh*n`@hBbsaU8G)k}Rj+l5+{7Bo`mS^odYR(lVX17kz5{nZ8k7TqCh>-z?3 z8Q}gg!;7PGu%F~BSqizyp6e>G!7GptNOcTO2c%+?K})Cxe0J@->BQ--W9vXQvEE-f zN*T0CmzD^C))0CHJrPL^xGa;YFH(?w5Y-7RMMcK~<+4XEL1x=z(4%y4TLL3=&OFvb zl~ou2>#bJxwlo73No0{C@ZD|%fveBJ_gQ=<+svG-txkDes08_Z1*tSX0>Scq}8A1OTm$rZUkb;-%YHBlT`v;e_3Hgh{ z2;iH`aD30$lk^)bDQ^|KeVBsuCe_}<37X>lPEhia=BU{sJBZtLaj z-aZPToLp7`XGj|X)a!Z@@ZcyQM)d+;H_lg%5D!VwI)~_vL6L@su2KX&o|FdKA|7=# zu<`WjH6W7(sEm3Ibu5n+fD4E@V58NsN?~T?FBL!(2oeI+L8^d%Kv;_7327kSk7DEf zg9TqovSJakS1D@+0n(y9*gL{jXoudQ8em44!0LMg=Ji7gS_7aQOq~9Z!g}fhAOWBn zfFV5?VtBQV>3N}`V7-!KI~(ziB@1{+$`0|ce~Sf*Z}*~O(8p102WC)fA6cNXr%Cv3 zK5;rL=j?>u{s(}n_tc^gXZ#yCSuyFySfR^3Zunx=eim5(3~H_iYB&OA9YIn8aGwBM zn80Gv6{Sqv#B>6Sg1hn-Jrv$?Kwb{yi=%jKKH7r72judyd&iS|H-hyV=+;sfa3b|v2JJ}4b%bLATWCS;0^H4k(Gd5pd=7R;9fsxC`Q(wxC z`sg(vr7W$1hhLBU4B>?Z432>JF*!;f(n>b)WIEK~?Q>oopvOT+EcpQ*k^goCG3PLq zBOfZI2mJ!&7R7lJ=W}xZ165fIz=_8;yX5LE?m(0hp}%@=HIMN~?435CUG^WidcA9f zJbHvs5I#q>^?Egd$^GUBC{5H-LuzK$uo<@povWFGc`0^Qf7z%7>x3 zZ1=&(EvkxlVSnJ>Y%i<)CknNLtum<58-Ts_zTzNfMkR9>b>^GD1A`{$^SS%X=dP}v z<)efD`L`)E4JEFBe_oC*J2-?oN&;1mGFY=c(C82CfZ(3npnpt|v2knkLM@j*tEfcG zOR=x`Y@of_+3B`L^-^P3uEacRc)XnT)v>Efw_~U;F)=l+fN)3=o0#|lL;&EThR575 z$b-M}#||Nw8eE!QYS;dJL}#NYFTI?dLK_U1SklC*C&&dQbH?Rs+iT1on}-yw+``@r zqnPFjlZ1@78IwvTquwh_L#A_Z+ z*{Gd|&6%~Kr)P?k4O_J2l{34%*qG-HjBLl;q^YW0ect35#^E!RZ z0xaoa@RQ|}b!D||0?^r~`R%N`yp0Q1T$xwAmKf;HnZ*fU8&E3-pnEg~Nfd)c_f@4{ zm-E-ldAUNpOPgOk6@zq;N z;KcegnW(}ZHLdIl{uuFyyMi;*w7LAFSE029un`zo05+gv-o}v>>$f@OsI50R7$R&O zs|b?FpwPN1hE|fytb5aZGj~6S+1$ECh6vKt#ZEe1#=Cp8*z=*ZaNbWg><15em}i(p zud8qm=QXWKPqm4aB#_h1v0CHHUJW-`JmTfg>oUF=T<6SOoS60udbT)447<`=j5)}= zS&JFZ@CCx|hm11Gk3ebKdRF8x6#VN+#Ph|MZ^P`>^C9{e_=0jGeAmj*i+18`S99EzZxhJ%1UF`G5o}RS(Dm(6q zQ`;baa;K-b$;?WgtyXK=5{(5jl)_lx-lqGyG&464_f_^UKQSjXTJ{%zT9VMUy(XdP zu6(UFWOU-i*bSqQeWPerVoO%|+6C~X5BMv|ts6cKBWot$)fG95d7rwj^S!fW_X{ov zsz>#HHTZb{6VvW1@V=ofRr$e3pSn-G>_EmRR${B;+AE1x$3u$tdlPG5kZjv}Z$Fx~ z(DQ?1&++wqH`K$s<<4nY8eNV&5o9EApU~|lVWzfk^cFkNpplx#a>H1J8h2HqG<-U@ z?Zqf;{BDsqdBxz*^(z+yEqgpe_88c}wBoYNBs7!e{NlDL!wII=V2X`votSl(*@Y(A zjbGeqQ*_*AQgo!j44mO_OEb(@Tx+pNucj45kW9hp;JafW!7|9CxPh^Go0o^+KNBF_ z$J~~=!kn6mWM6&DFE&W;p+YTGgC3-3oZ zQvxHb?wnnyZd{-%QGV12&FS4G3?yYzfGs_zHS9uf0YzrPDI7F8zxni8y9iW=-`Tx3 zMWp@A^Ao+M<)-DxB4rd9IBCLoD=#Z&)6>RaDjm|TX{S> zUiIa~32=ww;?Ggb*!Wmpty@g&>9;gxi}_?K4!%ZW$ubo^lBIZ;uck5`?u3GGG97** zD&Nfrn0!u)d{!4J>Y9!b#7Oj9&GJ+C7qb&Gl{7{9wo_^fMO_^qG97FdZkdzJm-N4@ zqSW@Kgs;6IdR6nw)_=`LqX@>tK&Q3}FuN3f5MePQ|6S?#!UtFV;mmKTdbfwYGGtZL zLJ)b3zHHa8@WlsFjpeDIB*XgUFVs=pyaVqOtkbH0y{lLAw7V@*G!d?eDD{+$i4vq; zo(L`HK7Yo-reKJ;Awt*Le?U8({vn+ZbQm{Hb1xp9R`p}~O;6Pv!gig6@ugshN;*lA%ztD67Qs>Y{_ zRZRLWi@PQAGhB)BalOL`6+;q>XVx+^Sitf+-!2vQJCXi?Rs>(GGM+K5UxVK2+sZMQ zzv${!w=C3>r}ZjY)V7c;DEcO)e7(AbE;r4ur;*Cd{JWXa=8W}IvD24Y{to8k zztl<-1>@wG(RK?LJ9+A_XP5r<2yCugd~P|HP%aAdwNalNE~~$CAFhI9;?;-&8^+3j z*p*d@dyFXDg|C?9+y%|;VXSwuM#K~U)!LH$hvcRexJq;ssK~0Z7fHs(-mb$j^}!1< ze*R=iQS@-n~h}~o}-Yb}#tl1te%J)Wmo8>_E*OFM^;{|%udR^R#d!`XXV z%wT&fN0R@|K+^O7?(Vk?lX;0{$(4VT;xl@(Xw;1>!eecpWQ-#hZnN68(x{i^Ru2tk zL^(@l4D|{6rSSb`D0Xpj;%#AD5lJtUWrQO{$7F^47~y;w(c*N!xZopjbondqOASgJ z`JQQvMz@2X5KT;CT!rDSlHepsG30e5_*&`V|JsEs~Zz>iC1Q%Sq#Tck(dt zbvNaCv?NKk5$-BZB2O!C5;{9iY8DVnk$w#My=~#0o*d@CI>wyU*WHqktne!KpG ztW{^OwBdKAU|E5Yc8%}i+X5r|w)VdA-#87gVRiH&#gBBaxib7bLXeYheG3tz-~TXG zTvJ-`gDm>{vO%9sevy8Df%{ePwB9a@FJO1)Om8{IPINhqFjnNWy_25iOg-^Ipz4ba zy;2hGcMH`aqlyvfal@=cBa;bjCc~Oh#^J*CpvF4|1{bCr9xUu?UPC9S+JJo`$@!a|y4yTR4zD!~QS>91NJcY9)#g zvGc`7e(n(O7|VHYOW%a^Mh^y22~SwUBRwL6L)`G6#QRD*<-ZAhw&y|@ zha{_$g)E(=9DIZ1$6RyMJ z=J|W_-8OqJj5d!;OMO&bgV$fy4bLw49Tmw1hZ9sI|BB>hw43BZ`X*_G7xx*my?MMH zW66Z@ido{>+v0klY7z4bmu)$5NWtzHs3{*?V{mr-%m`Y;nk7<2Q=Mu+KD~w|sl{A~ zKQ<{vEBChPNYok9yAb=D%q2BL9fuv%RNh-(R`3lWVg4M46x3)@IH_bDJd3HX zLi+@H@OZtoHLn&{O)e11Rl)g<-v_U>*ySPf+s3YN=7jb{wAo)!9sKPY5>MjQ2vkkZ zO^QN%ig~b6VU|6DwpZ)PVzl!g8dYOWA004H@ASxUbJZ3}bF1Bg(OJ5>jmgi$M4n2$ z6b$Wt<63uzpf)zNUebE1uDUz=cjSHg%2JO|<}hTd#SfXzDc;1LVFn&Y3zQ5#`(^9! zlR}YThkH-!!0QhcBj^FtA%!xDsb|A4g=yz(iK=Jj`2yXTrtEQPU2Xy@RU;Wryeq_I z^0Y}%3)V4EH3*0QnknPa5*Kn=f(85-V@>oshh@I2W;pM*X}WRouAxQTJO*<=z9L9c z`uyJMVocr}*H>SX<+SofLK>KT>Uoy;&Ed=4JFJE%oKHt}(;>wiT+!ow9NHtNsqk66 zq1ZjC(b8Xv0UE6|7qyK6*%FOLUzR|;E9bCro0L2#9f3tn<<#*XC7t8kQx9|ArmaF8 zQZ!22Yy38Pf!KU->k7yq0a7ax4_FLY1DV*{ll$!2pYv-cih1pcju#Q7Gf;+2$}+>Z zVprb2>b&M=Y3}Wdq~{H-k!VZsFcyhhE=eu4E@$G^8fMf(;fxG97Q07dL`|G5pNtiK z8puVw2 z7KQqGSF`iThO-jBD^AO$Di#@RB_nS|^LoJpqu&m>5&+{o`(P4EIjkWL(X zJy(GOP3_ri!x&XV_58RS+lC)#pmHI0M0hKEyls;uuK5Z4Npv4f`k4#8X$lIq2oHR7HA*d zT2bB=17F!*Pirv~zCnmhz`Xb4RF21LJ!e8DA*6aeRP(yJhq!{kc+pOWgACBJ)_%}I{O4)DzzkuYkT(}{JJbKus+q;T8R z#5&XOLBC>f!lJgn@8jk|jKkJ**!V-WjeyWv&o-@Y5f`lMj&;wY$?E6qS%7yQY54y9)EcT_%Ne zluO7T_51c!!SWaL<|J!mt}wjXx~vd9tSdIddE1%iuN0r(5^BE*W^I$C@K2Ht&RHYh zl;_k%a~HP8c%#Xhp-b4wMVXtp=qiRXol z1iMm=ROe&MPy6V*l7;OiS}n6xEGzEh7=_#+3FP+M5S#_sHAgMnSv{*?-h47JkF4y% z87>{r<;8fYPV%Z485xWwl?zl0GRCs1Eh%&uWTFprhZJJp4I}*HjAEl2^vZWxy%F83 zO8Jw#W2#fxG4^vN10Svn8mx=&i|6%KGdj&1ki}Mn+J1f8xgge-|Ar`(93$t^V^^@l zT+vWmho09bZP0i-rcYL?6=+VH&pZ4c87^qi8d}T{Db!0S$i?~DHBVJ^7_EPBy!p)< zi->+Oe%24|A?WR#r&JlQgu`> OLf<{4@HnJ69Q{9+5I#=; literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/dxfilmedge-1/1.txt b/test_resources/blackbox/dxfilmedge-1/1.txt new file mode 100644 index 0000000..49f6980 --- /dev/null +++ b/test_resources/blackbox/dxfilmedge-1/1.txt @@ -0,0 +1 @@ +36-2/21 \ No newline at end of file diff --git a/test_resources/blackbox/dxfilmedge-1/2.png b/test_resources/blackbox/dxfilmedge-1/2.png new file mode 100644 index 0000000000000000000000000000000000000000..665f080a3788f00ab91dbd03237d9b9680534fe0 GIT binary patch literal 16300 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 iFuD1RTy!_yTmFBuYL+5CfUGnC00005;zb5 literal 0 HcmV?d00001 diff --git a/test_resources/blackbox/dxfilmedge-1/2.txt b/test_resources/blackbox/dxfilmedge-1/2.txt new file mode 100644 index 0000000..a2b1434 --- /dev/null +++ b/test_resources/blackbox/dxfilmedge-1/2.txt @@ -0,0 +1 @@ +80-11/23 \ No newline at end of file diff --git a/test_resources/blackbox/dxfilmedge-1/3.jpg b/test_resources/blackbox/dxfilmedge-1/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce106b7804c215430fe9b9ee4e37a3b73e379eeb GIT binary patch literal 1652 zcmV-)28;Rs*#F=F5K2Z#MgRc;000310RRC1+WwA}S+3F)J%EK1e({JV;kgPFHPcZI6$@|H1$Z2mnU_ z>Hz@}0RO}Q8~^|T1pxs80000000000000011O)~M0|E&D+5iXv0RR910NMq_St5bC zx;^s48~OGfcTS+s4Grx)eC*muuU}j~hjfe20OY)#Gm+kU1AGC=qwt|+Mf$TP?k8iE zl4To@baw@BqG)z2uR@)X-h_mu1^%rpMO!Gk6&lYC(fxMiL(r&gfga!eBo$I#4?&A; z#cnm*Eos)_H-aU`Rda|g7DIf#(4Pi<1+^W0G z{<4o!V8_8qNUDR@-}<3Bjk|}zZ~@d+ApGKY{9iM?{1{JgV@rwH0V?BsqBkn%g5n)J z$oE3~NA)PCWXum?%SxW*O-}O33CI~#wa`^(lR3v)s;i2bpvZBPg$2r*cX1fw#wwA` zbP*Puw6G9$XHQ8~Z~LW#}gBX=8T7;RAYF`uM-6<``^f&GOtsu#-kHb$2R9huk9S|1NG zsNwatorTg<&4Iaak4X=&r+qyH4}>@nWO2ihIXOD5oys3dy%K8P6@z1CK4D*uV~^IY zN2W*od_3O^sLgDg7ly+P8R6lgTr3vFR2%R(*=(*OM8* zm?m)Zg3s!>`?2eu{22cLV&CfJ&yEa#v2XQq;XaYA#)^7dW1S?DG`Ez&0jk#inh7Xt zz8;9};Ss1SaOiOQLcI6MKZRoalJ5%r*~RKsuK3NxR`*$bDTnWk>4WfDxmO%jOh@i2 z%iUTP@4Pp1u=djKSJvi~AE+x>>5Nsq&Tm0c_M>=E_AK11c2&>n_-%(3k*5ApyIu73 zTzQ2ACTUq1J4sSl>Pz~uVq+n z7)BMKbhhp)cm&YBnZ`n8f>yjcly1Y67(`*JuFpJ^Nbv9jii)y3dwBT7E%Sw-u8QsZ-nPDjCOSG3KgiC8ls}n8^yBnp%f(W9!CMMY^V*K0Cn1RRP6Z zJJWiGyYc)6Z&tCI*p*Lqm(o!mfB95{6!DLzRqwqIPOCWSV|T#rrFHMGRxZk2>ijCwELV@{h&(;@>Wxdhx%3DCXMQsBBCSaoB2lVcka$H=B-WX zj8%sek{_h5x3qbvJ0@;fSFN>U(y*+&moZ307|9N>*-or%Dk$*9y+TWda