forgery_detection_zero/
lib.rs1#![doc = include_str!("../README.md")]
2use bitvec::bitvec;
3use bitvec::vec::BitVec;
4
5#[cfg(feature = "image")]
6use image::{DynamicImage, ImageBuffer, Luma};
7
8#[cfg(feature = "image")]
9mod convert;
10mod vote;
11
12#[cfg(feature = "image")]
13use convert::ToLumaZero;
14
15pub use vote::{Vote, Votes};
16
17#[derive(thiserror::Error, Debug)]
19pub enum Error {
20 #[error("inconsistency between the raw image array and the width and height provided")]
22 InvalidRawDimensions,
23
24 #[error("failed to encode the original image to a 99% quality JPEG: {0}")]
26 Encoding(#[from] jpeg_encoder::EncodingError),
27
28 #[cfg(feature = "image")]
30 #[error("failed to decode the image: {0}")]
31 Decoding(#[from] image::ImageError),
32}
33
34type Result<T> = std::result::Result<T, Error>;
35
36#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
38pub struct Grid(pub u8);
39
40impl Grid {
41 const fn from_xy(x: u32, y: u32) -> Self {
42 Self(((x % 8) + (y % 8) * 8) as u8)
43 }
44
45 pub const fn x(&self) -> u8 {
46 self.0 % 8
47 }
48
49 pub const fn y(&self) -> u8 {
50 self.0 / 8
51 }
52}
53
54#[derive(Default, Clone, Debug)]
56pub struct ForgedRegion {
57 pub start: (u32, u32),
59
60 pub end: (u32, u32),
62
63 pub grid: Grid,
64 pub lnfa: f64,
65 regions_xy: Box<[(u32, u32)]>,
66}
67
68pub struct ForeignGridAreas {
69 luminance: LuminanceImage,
70 votes: Votes,
71 forged_regions: Box<[ForgedRegion]>,
72 lnfa_grids: [f64; 64],
73 main_grid: Option<Grid>,
74}
75
76impl ForeignGridAreas {
77 pub fn votes(&self) -> &Votes {
78 &self.votes
79 }
80
81 pub fn build_forgery_mask(&self) -> ForgeryMask {
83 ForgeryMask::from_regions(&self.forged_regions, self.votes.width, self.votes.height)
84 }
85
86 pub fn forged_regions(&self) -> &[ForgedRegion] {
88 self.forged_regions.as_ref()
89 }
90
91 pub fn lnfa_grids(&self) -> [f64; 64] {
92 self.lnfa_grids
93 }
94
95 pub fn main_grid(&self) -> Option<Grid> {
96 self.main_grid
97 }
98
99 pub fn is_cropped(&self) -> bool {
104 self.main_grid.map_or(false, |grid| grid.0 > 0)
105 }
106
107 #[cfg(feature = "image")]
113 pub fn detect_missing_grid_areas(&self) -> Result<Option<MissingGridAreas>> {
114 let main_grid = if let Some(main_grid) = self.main_grid {
115 main_grid
116 } else {
117 return Ok(None);
118 };
119
120 let jpeg_99 = self.luminance.to_jpeg_99_luminance()?;
121
122 let mut jpeg_99_votes = Votes::from_luminance(&jpeg_99);
123 for (&vote, vote_99) in self.votes.iter().zip(jpeg_99_votes.iter_mut()) {
125 if vote == Vote::AlignedWith(main_grid) {
126 *vote_99 = Vote::Invalid;
127 }
128 }
129
130 let jpeg_99_forged_regions = jpeg_99_votes.detect_forgeries(None, Grid(0));
134
135 Ok(Some(MissingGridAreas {
136 votes: jpeg_99_votes,
137 missing_regions: jpeg_99_forged_regions,
138 }))
139 }
140}
141
142impl IntoIterator for ForeignGridAreas {
143 type Item = ForgedRegion;
144
145 type IntoIter = std::vec::IntoIter<ForgedRegion>;
146
147 fn into_iter(self) -> Self::IntoIter {
148 self.forged_regions.into_vec().into_iter()
149 }
150}
151
152pub struct MissingGridAreas {
154 votes: Votes,
155
156 missing_regions: Box<[ForgedRegion]>,
157}
158
159impl MissingGridAreas {
160 pub fn votes(&self) -> &Votes {
161 &self.votes
162 }
163
164 pub fn forged_regions(&self) -> &[ForgedRegion] {
166 self.missing_regions.as_ref()
167 }
168
169 pub fn build_forgery_mask(self) -> ForgeryMask {
171 ForgeryMask::from_regions(&self.missing_regions, self.votes.width, self.votes.height)
172 }
173}
174
175impl IntoIterator for MissingGridAreas {
176 type Item = ForgedRegion;
177
178 type IntoIter = std::vec::IntoIter<ForgedRegion>;
179
180 fn into_iter(self) -> Self::IntoIter {
181 self.missing_regions.into_vec().into_iter()
182 }
183}
184
185pub struct Zero {
203 luminance: LuminanceImage,
204}
205
206impl Zero {
207 #[cfg(feature = "image")]
209 pub fn from_image(image: &DynamicImage) -> Self {
210 let luminance = image.to_luma32f_zero();
211
212 Self { luminance }
213 }
214
215 pub fn from_luminance_raw(luminance: Box<[f64]>, width: u32, height: u32) -> Result<Self> {
221 if luminance.len() != width.saturating_mul(height) as usize {
222 return Err(Error::InvalidRawDimensions);
223 }
224
225 Ok(Self {
226 luminance: LuminanceImage {
227 image: luminance,
228 width,
229 height,
230 },
231 })
232 }
233
234 pub fn detect_forgeries(self) -> ForeignGridAreas {
240 let votes = Votes::from_luminance(&self.luminance);
241 let (main_grid, lnfa_grids) = votes.detect_global_grids();
242 let forged_regions = votes.detect_forgeries(main_grid, Grid(63));
243
244 ForeignGridAreas {
245 luminance: self.luminance,
246 votes,
247 forged_regions,
248 lnfa_grids,
249 main_grid,
250 }
251 }
252}
253
254#[cfg(feature = "image")]
255impl IntoIterator for Zero {
256 type Item = ForgedRegion;
257
258 type IntoIter = Box<dyn Iterator<Item = ForgedRegion>>;
259
260 fn into_iter(self) -> Self::IntoIter {
261 let foreign_grid_areas = self.detect_forgeries();
262 let missing_grid_regions = foreign_grid_areas
263 .detect_missing_grid_areas()
264 .ok()
265 .flatten()
266 .into_iter()
267 .flat_map(IntoIterator::into_iter);
268 let forged_regions = foreign_grid_areas.into_iter().chain(missing_grid_regions);
269
270 Box::new(forged_regions)
271 }
272}
273
274pub struct ForgeryMask {
276 mask: BitVec,
277 width: u32,
278 height: u32,
279}
280
281impl ForgeryMask {
282 #[cfg(feature = "image")]
286 pub fn into_luma_image(self) -> ImageBuffer<Luma<u8>, Vec<u8>> {
287 ImageBuffer::from_fn(self.width, self.height, |x, y| {
288 let index = (x + y * self.width) as usize;
289
290 Luma([u8::from(self.mask[index]) * 255])
291 })
292 }
293
294 pub fn is_forged(&self, x: u32, y: u32) -> bool {
296 self.mask
297 .get((x + y * self.width) as usize)
298 .as_deref()
299 .copied()
300 .unwrap_or(false)
301 }
302
303 pub fn width(&self) -> u32 {
305 self.width
306 }
307
308 pub fn height(&self) -> u32 {
310 self.height
311 }
312
313 fn from_regions(regions: &[ForgedRegion], width: u32, height: u32) -> Self {
321 let w = 9;
322 let mut mask_aux = bitvec![0; width as usize * height as usize];
323 let mut forgery_mask = bitvec![0; width as usize * height as usize];
324
325 for forged in regions {
326 for &(x, y) in forged.regions_xy.iter() {
327 for xx in (x - w)..=(x + w) {
328 for yy in (y - w)..=(y + w) {
329 let index = (xx + yy * width) as usize;
330 mask_aux.set(index, true);
331 forgery_mask.set(index, true);
332 }
333 }
334 }
335 }
336
337 for x in w..width.saturating_sub(w) {
338 for y in w..height.saturating_sub(w) {
339 let index = (x + y * width) as usize;
340 if !mask_aux[index] {
341 for xx in (x - w)..=(x + w) {
342 for yy in (y - w)..=(y + w) {
343 let index = (xx + yy * width) as usize;
344 forgery_mask.set(index, false);
345 }
346 }
347 }
348 }
349 }
350
351 Self {
352 mask: forgery_mask,
353 width,
354 height,
355 }
356 }
357}
358
359pub(crate) struct LuminanceImage {
360 image: Box<[f64]>,
361 width: u32,
362 height: u32,
363}
364
365impl LuminanceImage {
366 pub(crate) fn width(&self) -> u32 {
367 self.width
368 }
369
370 pub(crate) fn height(&self) -> u32 {
371 self.height
372 }
373
374 pub(crate) fn as_raw(&self) -> &[f64] {
375 &self.image
376 }
377
378 pub(crate) unsafe fn unsafe_get_pixel(&self, x: u32, y: u32) -> &f64 {
384 self.image.get_unchecked((x + y * self.width) as usize)
385 }
386}