Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| #![feature(zero_one)] | |
| extern crate image; | |
| extern crate num_traits; | |
| use std::path::Path; | |
| use image::GenericImage; | |
| use image::Pixel; | |
| use image::ImageBuffer; | |
| use std::ops::Index; | |
| use std::ops::AddAssign; | |
| use num_traits::cast::ToPrimitive; | |
| use std::num::One; | |
| use std::env; | |
| use std::ops::Deref; | |
| // ==================== Pixels ==================== | |
| /// Represents generic sequence of pixels | |
| trait PixelLine { | |
| /// Type of Pixel - as in GenericImage | |
| type Pixel: Pixel; | |
| /// Returns length of the sequence | |
| fn len(&self) -> u32; | |
| /// Returns copy of the pixel at given position | |
| fn get_pixel(&self, index: u32) -> Self::Pixel; | |
| } | |
| /// Represents one column of pixels in the image | |
| struct PixelColumn<'a, I: 'a> where I: GenericImage { | |
| column_index: u32, | |
| img: &'a I, | |
| } | |
| /// Represents one row of pixels in the image | |
| struct PixelRow<'a, I: 'a> where I: GenericImage { | |
| row_index: u32, | |
| img: &'a I, | |
| } | |
| /// Iterator over pixels in `PixelLine` | |
| struct PixelLineIterator<'a, P: 'a> where P: Pixel { | |
| index: u32, | |
| line: &'a PixelLine<Pixel=P>, | |
| } | |
| impl<'a, P> PixelLineIterator<'a, P> where P: Pixel { | |
| /// Constructs new PixelLineIterator from PixelLine starting at 0 | |
| fn new(pixel_line: &'a PixelLine<Pixel=P>) -> Self { | |
| PixelLineIterator {index: 0, line: pixel_line} | |
| } | |
| } | |
| impl<'a, I> PixelColumn<'a, I> where I: GenericImage { | |
| /// Constructs new PixelColumn from a GenericImage and a column number | |
| /// | |
| /// # Panics | |
| /// Panics if column is outside image boundaries. | |
| /// | |
| fn new(img: &'a I, column: u32) -> PixelColumn<'a, I> { | |
| // column must be in range | |
| assert!(column < img.width()); | |
| PixelColumn {column_index: column, img: img} | |
| } | |
| } | |
| impl<'a, I> PixelRow<'a, I> where I: GenericImage { | |
| /// Constructs new PixelRow from a GenericImage and a row number | |
| /// | |
| /// # Panics | |
| /// Panics if row is outside image boundaries. | |
| /// | |
| fn new(img :&'a I, row: u32) -> PixelRow<'a, I> { | |
| assert!(row < img.height()); | |
| PixelRow {row_index: row, img: img} | |
| } | |
| } | |
| impl<'a, I> PixelLine for PixelColumn<'a, I> where I: GenericImage { | |
| type Pixel = I::Pixel; | |
| fn len(&self) -> u32 { | |
| self.img.height() | |
| } | |
| fn get_pixel(&self, index: u32) -> Self::Pixel { | |
| self.img.get_pixel(self.column_index, index) | |
| } | |
| } | |
| impl<'a, I> PixelLine for PixelRow<'a, I> where I: GenericImage { | |
| type Pixel = I::Pixel; | |
| fn len(&self) -> u32 { | |
| self.img.width() | |
| } | |
| fn get_pixel(&self, index: u32) -> Self::Pixel { | |
| self.img.get_pixel(index, self.row_index) | |
| } | |
| } | |
| impl<'a, P> Iterator for PixelLineIterator<'a, P> where P: Pixel { | |
| type Item = P; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| if self.index < self.line.len() { | |
| let old = self.index; | |
| self.index += 1; | |
| Some(self.line.get_pixel(old)) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| /// Calculates matching score of two `PixelLines`. | |
| /// | |
| /// The matching score is just average difference of two adjacent pixels, | |
| /// so the less score is, the more similar lines are. Zero means the lines are identical. | |
| /// | |
| /// # Panics | |
| /// Panics if lines are of different length. | |
| /// | |
| fn line_match_score<'a, P>(line1: &'a PixelLine<Pixel=P>, line2: &'a PixelLine<Pixel=P>) -> f64 where P: Pixel { | |
| assert!(line1.len() == line2.len()); | |
| assert!(line1.len() > 0); | |
| let mut difference: u64 = 0; | |
| { | |
| let li1: PixelLineIterator<'a, P> = PixelLineIterator::new(line1); | |
| let li2: PixelLineIterator<'a, P> = PixelLineIterator::new(line2); | |
| for pixels in li1.zip(li2) { | |
| let (p1, p2) = pixels; | |
| for values in p1.channels().iter().zip(p2.channels().iter()) { | |
| let (v1, v2) = values; | |
| difference += if v1 > v2 { | |
| *v1 - *v2 | |
| } else { | |
| *v2 - *v1 | |
| }.to_u64().unwrap(); | |
| } | |
| } | |
| } | |
| (difference as f64) / (line1.len() as f64) / (P::channel_count() as f64) | |
| } | |
| // ==================== Arg parsing ==================== | |
| /// Like `std::ops::Range`, but end is inclusive | |
| #[derive(Debug)] | |
| #[derive(Clone)] | |
| struct InclusiveRange<T> where T: AddAssign + PartialOrd + One + Copy { | |
| begin: T, | |
| end: T, | |
| } | |
| /// Represents iterator over values within `InclusiveRange` | |
| struct InclusiveRangeIterator<T> where T: AddAssign + PartialOrd + One + Copy { | |
| index: T, | |
| end: T, | |
| } | |
| impl<T> InclusiveRange<T> where T: AddAssign + PartialOrd + One + Copy { | |
| /// Returns iterator starting at begin | |
| fn iter(&self) -> InclusiveRangeIterator<T> { | |
| InclusiveRangeIterator::<T> {index: self.begin, end: self.end} | |
| } | |
| } | |
| impl<T> Iterator for InclusiveRangeIterator<T> where T: AddAssign + PartialOrd + One + Copy { | |
| type Item = T; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| if self.index <= self.end { | |
| let old = self.index; | |
| self.index += T::one(); | |
| Some(old) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| /// Convenience type cast | |
| type InclusiveRangeU32 = InclusiveRange<u32>; | |
| /// Represents component of input string | |
| enum InputPart { | |
| String(String), | |
| Range(InclusiveRangeU32), | |
| } | |
| impl InputPart { | |
| /// Basically unwrap() for String | |
| fn take_mandatory_string(self) -> String { | |
| if let InputPart::String(s) = self { s } else { panic!("String is mandatory at this place") } | |
| } | |
| /// Basically unwrap() for Range | |
| fn take_mandatory_range(self) -> InclusiveRangeU32 { | |
| if let InputPart::Range(r) = self { r } else { panic!("Range is mandatory at this place") } | |
| } | |
| } | |
| /// Collection representing parsed input parts | |
| type ParsedInput = Vec<InputPart>; | |
| /// Parser of input string | |
| /// | |
| /// String format is simple version of Bash expansion format. | |
| /// For example, string `"Hello {1..3} world {4..6}!"` Is parsed as `[String("Hello "), Range(1, 3), String(" world "), Range(4, 6), String("!")]` | |
| enum Parser { | |
| Empty, | |
| Str(String), | |
| FirstBrace, | |
| FirstNumber(u32), | |
| FirstDot(u32), | |
| SecondDot(u32), | |
| SecondNumber(InclusiveRangeU32), | |
| } | |
| impl Parser { | |
| /// Constructs new empty Parser | |
| fn new() -> Parser { | |
| Parser::Empty | |
| } | |
| /// Performs one step of parsing | |
| /// | |
| /// Argument parsed is updated if new part was parsed. | |
| fn step(self, c: char, parsed: &mut ParsedInput) -> Parser { | |
| match self { | |
| Parser::Empty => match c { | |
| '{' => Parser::FirstBrace, | |
| '}' => panic!("Invalid syntax: closing curly brace doesn't match opening curly brace."), | |
| x => Parser::Str(x.to_string()), | |
| }, | |
| Parser::Str(mut s) => match c { | |
| '{' => { parsed.push(InputPart::String(s)); Parser::FirstBrace }, | |
| '}' => panic!("Invalid syntax: closing curly brace doesn't match opening curly brace."), | |
| x => { s.push(x); Parser::Str(s) }, | |
| }, | |
| Parser::FirstBrace => match c { | |
| digit @ '0' ... '9' => Parser::FirstNumber(digit.to_digit(10).unwrap()), | |
| x => panic!("Syntax error: number (0-9) expected, '{}' found", x), | |
| }, | |
| Parser::FirstNumber(num) => match c { | |
| digit @ '0' ... '9' => Parser::FirstNumber(num * 10 + digit.to_digit(10).unwrap()), | |
| '.' => Parser::FirstDot(num), | |
| x => panic!("Syntax error: number ('0'-'9') or dot ('.') expected, '{}' found", x), | |
| }, | |
| Parser::FirstDot(num) => match c { | |
| '.' => Parser::SecondDot(num), | |
| x => panic!("Syntax error: dot ('.') expected, '{}' found", x), | |
| }, | |
| Parser::SecondDot(num) => match c { | |
| digit @ '0' ... '9' => Parser::SecondNumber(InclusiveRangeU32 {begin: num, end: digit.to_digit(10).unwrap()}), | |
| x => panic!("Syntax error: number (0-9) expected, '{}' found", x), | |
| }, | |
| Parser::SecondNumber(r) => match c { | |
| digit @ '0' ... '9' => Parser::SecondNumber(InclusiveRangeU32 {begin: r.begin, end: r.end * 10 + digit.to_digit(10).unwrap()}), | |
| '}' => { parsed.push(InputPart::Range(r)); Parser::Empty } | |
| x => panic!("Syntax error: number (0-9) or closing brace ('}}') expected, '{}' found", x), | |
| } | |
| } | |
| } | |
| /// Finalizes parsing | |
| fn finish(self, parsed: &mut ParsedInput) { | |
| match self { | |
| Parser::Str(s) => parsed.push(InputPart::String(s)), | |
| Parser::Empty => {}, | |
| _ => panic!("Syntax error: unfinished range"), | |
| } | |
| } | |
| /// Parses input string | |
| fn parse_input(input: &str) -> ParsedInput { | |
| let mut parsed_input = ParsedInput::new(); | |
| let mut parser = Parser::new(); | |
| for c in input.chars() { | |
| parser = parser.step(c, &mut parsed_input); | |
| } | |
| parser.finish(&mut parsed_input); | |
| parsed_input | |
| } | |
| } | |
| /// Vec guaranteed to be sorted (newtype pattern) | |
| struct SortedVec<T>(Vec<T>) where T: Ord; | |
| impl<T> SortedVec<T> where T: Ord { | |
| /// Creates new `SortedVec` from unsorted `Vec` | |
| fn new(mut vec: Vec<T>) -> SortedVec<T> { | |
| vec.sort(); | |
| SortedVec::<T>(vec) | |
| } | |
| /// Gives up sorted property to allow mutations. | |
| fn to_vec(self) -> Vec<T> { | |
| self.0 | |
| } | |
| //Allow some safe mutations | |
| fn remove(&mut self, index: usize) -> T { | |
| self.0.remove(index) | |
| } | |
| fn pop(&mut self) -> Option<T> { | |
| self.0.pop() | |
| } | |
| fn dedup(&mut self) { | |
| self.0.dedup() | |
| } | |
| } | |
| impl<T> Deref for SortedVec<T> where T: Ord { | |
| type Target = Vec<T>; | |
| // Only immutable deref provided, so no changes are allowed, | |
| // which means nothing can break sorted guarantee. | |
| fn deref(&self) -> &Self::Target { | |
| &self.0 | |
| } | |
| } | |
| #[derive(Debug)] | |
| #[derive(Clone)] | |
| enum StrDiff { | |
| Same(usize), | |
| Diff(usize), | |
| } | |
| type StrDiffs = Vec<StrDiff>; | |
| /// Represents state of analysis of different numbers | |
| enum AnalyzerState { | |
| Empty, | |
| SamePartWithoutDigit(usize), | |
| SamePartWithDigit(usize, usize), | |
| DifferentPart(usize), | |
| } | |
| impl SortedVec<String> { | |
| fn analyze_different_numbers(& self) -> Vec<StrDiffs> { | |
| let mut result = Vec::<StrDiffs>::new(); | |
| let mut iter = (*self).iter(); | |
| let mut last = if let Some(s) = iter.next() { | |
| s | |
| } else { | |
| return result | |
| }; | |
| for str in iter { | |
| if last.len() != str.len() { | |
| panic!("File names are not of same length"); | |
| } | |
| let mut tmp_res = StrDiffs::new(); | |
| let mut byte_index = 0usize; | |
| let mut state = AnalyzerState::Empty; | |
| let striter = last.chars().zip(str.chars()); | |
| for (c1, c2) in striter { | |
| state = match state { | |
| AnalyzerState::Empty => { | |
| if c1.is_digit(10) && c2.is_digit(10) { | |
| if c1 == c2 { | |
| AnalyzerState::SamePartWithDigit(byte_index, byte_index) | |
| } else { | |
| AnalyzerState::DifferentPart(byte_index) | |
| } | |
| } else { | |
| if c1 != c2 { | |
| panic!("Strings differ in something that is not digit"); | |
| } | |
| AnalyzerState::SamePartWithoutDigit(byte_index) | |
| } | |
| }, | |
| AnalyzerState::SamePartWithoutDigit(begin) => { | |
| if c1.is_digit(10) && c2.is_digit(10) { | |
| if c1 == c2 { | |
| AnalyzerState::SamePartWithDigit(begin, byte_index) | |
| } else { | |
| tmp_res.push(StrDiff::Same(byte_index - begin)); | |
| AnalyzerState::DifferentPart(byte_index) | |
| } | |
| } else { | |
| if c1 != c2 { | |
| panic!("Strings differ in something that is not digit"); | |
| } | |
| AnalyzerState::SamePartWithoutDigit(begin) | |
| } | |
| }, | |
| AnalyzerState::SamePartWithDigit(begin, first_digit) => { | |
| if c1.is_digit(10) && c2.is_digit(10) { | |
| if c1 == c2 { | |
| AnalyzerState::SamePartWithDigit(begin, first_digit) | |
| } else { | |
| if begin != first_digit { | |
| tmp_res.push(StrDiff::Same(first_digit - begin)); | |
| } | |
| AnalyzerState::DifferentPart(first_digit) | |
| } | |
| } else { | |
| if c1 != c2 { | |
| panic!("Strings differ in something that is not digit"); | |
| } | |
| AnalyzerState::SamePartWithoutDigit(begin) | |
| } | |
| }, | |
| AnalyzerState::DifferentPart(first_digit) => { | |
| if c1.is_digit(10) && c2.is_digit(10) { | |
| AnalyzerState::DifferentPart(first_digit) | |
| } else { | |
| if c1 != c2 { | |
| panic!("Strings differ in something that is not digit"); | |
| } | |
| tmp_res.push(StrDiff::Diff(byte_index - first_digit)); | |
| AnalyzerState::SamePartWithoutDigit(byte_index) | |
| } | |
| } | |
| }; | |
| byte_index += c1.len_utf8(); | |
| } | |
| match state { | |
| AnalyzerState::Empty => {}, | |
| AnalyzerState::SamePartWithoutDigit(begin) => { | |
| tmp_res.push(StrDiff::Same(byte_index - begin)); | |
| }, | |
| AnalyzerState::SamePartWithDigit(begin, _) => { | |
| tmp_res.push(StrDiff::Same(byte_index - begin)); | |
| }, | |
| AnalyzerState::DifferentPart(first_digit) => { | |
| tmp_res.push(StrDiff::Diff(byte_index - first_digit)); | |
| }, | |
| } | |
| result.push(tmp_res); | |
| last = str; | |
| } | |
| result | |
| } | |
| } | |
| // ==================== Program logic ==================== | |
| /// Specifies how images should be placed | |
| #[derive(PartialEq)] | |
| enum Transposition { | |
| LeftRight, | |
| TopDown, | |
| } | |
| /// Opens images and extracts information from them | |
| fn detect_tile_size_and_transposition(path1: &str, path2: &str) -> (u32, u32, Transposition) { | |
| let img1 = image::open(&Path::new(path1)).unwrap(); | |
| let img2 = image::open(&Path::new(path2)).unwrap(); | |
| let vertical_score = { | |
| let vline1 = PixelColumn::new(&img1, img1.width() - 1); | |
| let vline2 = PixelColumn::new(&img2, 0); | |
| line_match_score(&vline1, &vline2) | |
| }; | |
| let horizontal_score = { | |
| let hline1 = PixelRow::new(&img1, img1.height() - 1); | |
| let hline2 = PixelRow::new(&img2, 0); | |
| line_match_score(&hline1, &hline2) | |
| }; | |
| println!("Vertical score: {:?}", vertical_score); | |
| println!("Horizontal score: {:?}", horizontal_score); | |
| println!("Calculated configuration is:"); | |
| let transposition = if vertical_score < horizontal_score { | |
| println!("[IMAGE1][IMAGE2]"); | |
| Transposition::LeftRight | |
| } else { | |
| println!("[IMAGE1]"); | |
| println!("[IMAGE2]"); | |
| Transposition::TopDown | |
| }; | |
| (img1.width(), img1.height(), transposition) | |
| } | |
| struct InputPattern { | |
| prefix: String, | |
| first_range: InclusiveRangeU32, | |
| separator: String, | |
| second_range: InclusiveRangeU32, | |
| suffix: String, | |
| } | |
| fn consume_strdiff_iter_upto_len<'a, T>(max_len: usize, input_iter: T, res: &mut StrDiffs) where T: IntoIterator<Item=&'a StrDiff> { | |
| let mut iter = input_iter.into_iter(); | |
| let mut pushed_len = 0; | |
| while pushed_len < max_len { | |
| let item = iter.next().unwrap(); | |
| res.push(item.clone()); | |
| pushed_len += match *item { | |
| StrDiff::Same(l) => l, | |
| StrDiff::Diff(l) => l, | |
| }; | |
| } | |
| } | |
| enum MaybeOwnedStrDiffs<'a> { | |
| Owned(StrDiffs), | |
| Borrowed(&'a StrDiffs), | |
| } | |
| impl<'a> Deref for MaybeOwnedStrDiffs<'a> { | |
| type Target = StrDiffs; | |
| fn deref(&self) -> &Self::Target { | |
| match *self { | |
| MaybeOwnedStrDiffs::Owned(ref owned) => owned, | |
| MaybeOwnedStrDiffs::Borrowed(borrowed) => borrowed, | |
| } | |
| } | |
| } | |
| fn collapse_diff_patterns(patterns: &Vec<StrDiffs>) -> StrDiffs { | |
| let mut iter = patterns.iter(); | |
| let mut res = MaybeOwnedStrDiffs::Borrowed(iter.next().unwrap()); | |
| for pattern in iter { | |
| if pattern.len() != res.len() { | |
| let mut tmp = StrDiffs::new(); | |
| { | |
| let mut pat_iter = res.iter(); | |
| let mut cur_iter = pattern.iter(); | |
| loop { | |
| if let Some(pat) = pat_iter.next() { | |
| if let Some(cur) = cur_iter.next() { | |
| let pat_len = match *pat { | |
| StrDiff::Same(l) => l, | |
| StrDiff::Diff(l) => l, | |
| }; | |
| let cur_len = match *cur { | |
| StrDiff::Same(l) => l, | |
| StrDiff::Diff(l) => l, | |
| }; | |
| if pat_len > cur_len { | |
| tmp.push(if let StrDiff::Same(_) = *cur { StrDiff::Same(cur_len) } else { StrDiff::Diff(cur_len) }); | |
| consume_strdiff_iter_upto_len(pat_len - cur_len, &mut cur_iter, &mut tmp); | |
| } else { | |
| tmp.push(if let StrDiff::Same(_) = *pat { StrDiff::Same(pat_len) } else { StrDiff::Diff(pat_len) }); | |
| consume_strdiff_iter_upto_len(cur_len - pat_len, &mut pat_iter, &mut tmp); | |
| } | |
| } else { | |
| break; | |
| } | |
| } else { | |
| break; | |
| } | |
| } | |
| } | |
| res = MaybeOwnedStrDiffs::Owned(tmp); | |
| } | |
| } | |
| match res { | |
| MaybeOwnedStrDiffs::Owned(owned) => owned, | |
| MaybeOwnedStrDiffs::Borrowed(borrowed) => borrowed.clone(), | |
| } | |
| } | |
| fn parse_ranges(pattern: &StrDiffs, path: &str) -> Vec<InclusiveRangeU32> { | |
| let mut res = Vec::<InclusiveRangeU32>::new(); | |
| let mut pos = 0usize; | |
| for str_diff in pattern { | |
| pos += match *str_diff { | |
| StrDiff::Same(same) => { | |
| same | |
| }, | |
| StrDiff::Diff(different) => { | |
| let ref slice = path[pos..pos + different]; | |
| let number = slice.parse::<u32>().unwrap(); | |
| //let number = path[pos..pos + different].parse::<u32>().unwrap(); | |
| res.push(InclusiveRangeU32 { begin: number, end: number }); | |
| different | |
| }, | |
| } | |
| } | |
| res | |
| } | |
| fn update_ranges(pattern: &StrDiffs, path: &str, ranges: &mut Vec<InclusiveRangeU32>) { | |
| let mut iter = ranges.iter_mut(); | |
| let mut pos = 0usize; | |
| for str_diff in pattern { | |
| pos += match *str_diff { | |
| StrDiff::Same(same) => { | |
| same | |
| }, | |
| StrDiff::Diff(different) => { | |
| let ref slice = path[pos..pos + different]; | |
| let number = slice.parse::<u32>().unwrap(); | |
| let mut range = iter.next().unwrap(); | |
| if number < range.begin { range.begin = number; } | |
| if number > range.end { range.end = number; } | |
| different | |
| }, | |
| } | |
| } | |
| } | |
| fn parse_multi_arg(all_args: env::Args) -> (InputPattern, String) { | |
| let mut args = all_args.collect::<Vec<String>>(); | |
| let last = args.pop().unwrap(); | |
| let sorted_args = SortedVec::<String>::new(args); | |
| let differences = sorted_args.analyze_different_numbers(); | |
| let collapsed_pattern = collapse_diff_patterns(&differences); | |
| println!("Collapsed: {:#?}", collapsed_pattern); | |
| let mut arg_iter = sorted_args.iter(); | |
| let mut ranges = parse_ranges(&collapsed_pattern, &arg_iter.next().unwrap()); | |
| for arg in arg_iter { | |
| update_ranges(&collapsed_pattern, &arg, &mut ranges); | |
| } | |
| println!("Ranges: {:#?}", ranges); | |
| if ranges.len() != 2 { | |
| panic!("Can not understand file names - they should have two differing numbers in their paths"); | |
| } | |
| let arg0 = &sorted_args[0]; | |
| let mut range_iter = ranges.iter(); | |
| let mut pattern_iter = collapsed_pattern.iter(); | |
| let mut pos = 0usize; | |
| let (prefix, first_range) = { | |
| match *pattern_iter.next().unwrap() { | |
| StrDiff::Same(l) => { | |
| let pref = &arg0[pos..pos + l]; | |
| pos += l; | |
| if let StrDiff::Diff(d) = *pattern_iter.next().unwrap() { | |
| pos += d; | |
| } else { | |
| panic!("This error can never occur"); | |
| } | |
| (pref, range_iter.next().unwrap().clone()) | |
| }, | |
| StrDiff::Diff(d) => { | |
| pos += d; | |
| ("", range_iter.next().unwrap().clone()) | |
| }, | |
| } | |
| }; | |
| let sep_len = if let StrDiff::Same(l) = *pattern_iter.next().unwrap() { l } else { panic!("This error can never occur") }; | |
| let separator = &arg0[pos..pos + sep_len]; | |
| pos += sep_len; | |
| let second_range = (*range_iter.next().unwrap()).clone(); | |
| pos += if let StrDiff::Diff(l) = *pattern_iter.next().unwrap() { l } else { panic!("This error can never occur") }; | |
| let suffix = if let Some(last) = pattern_iter.next() { | |
| let suff_len = if let StrDiff::Same(l) = *last { l } else { panic!("This error can never occur") }; | |
| &arg0[pos..pos + suff_len] | |
| } else { | |
| "" | |
| }; | |
| ( | |
| InputPattern { | |
| prefix: prefix.to_string(), | |
| first_range: first_range, | |
| separator: separator.to_string(), | |
| second_range: second_range, | |
| suffix: suffix.to_string(), | |
| }, | |
| last | |
| ) | |
| } | |
| fn parse_two_arg(mut args: env::Args) -> (InputPattern, String) { | |
| let input = args.next().expect("Usage: image_concat INPUT_IMAGE_PATTERN OUTPUT_IMAGE"); | |
| let result_path = args.next().expect("Usage: image_concat INPUT_IMAGE_PATTERN OUTPUT_IMAGE"); | |
| let mut parsed_input = Parser::parse_input(&input); | |
| if parsed_input.len() > 5 { | |
| panic!("Too many components! (Two separated ranges expected)"); | |
| } | |
| let (suffix, second_range) = match parsed_input.pop().unwrap() { | |
| InputPart::String(s) => (s, parsed_input.pop().unwrap().take_mandatory_range()), | |
| InputPart::Range(r) => (String::new(), r), | |
| }; | |
| let separator = parsed_input.pop().unwrap().take_mandatory_string(); | |
| let first_range = parsed_input.pop().unwrap().take_mandatory_range(); | |
| let prefix = parsed_input.pop().map_or_else(String::new, InputPart::take_mandatory_string); | |
| ( | |
| InputPattern { | |
| prefix: prefix, | |
| first_range: first_range, | |
| separator: separator, | |
| second_range: second_range, | |
| suffix: suffix, | |
| }, | |
| result_path | |
| ) | |
| } | |
| fn main() { | |
| let mut args = env::args(); | |
| args.next(); | |
| let (pattern, result_path) = if args.len() > 2 { | |
| parse_multi_arg(args) | |
| } else { | |
| parse_two_arg(args) | |
| }; | |
| let InputPattern { prefix, first_range, separator, second_range, suffix} = pattern; | |
| let corner_image_filename = format!("{}{}{}{}{}", prefix, first_range.begin, separator, second_range.begin, suffix); | |
| let other_image_filename = format!("{}{}{}{}{}", prefix, first_range.begin + 1, separator, second_range.begin, suffix); | |
| let (tile_width, tile_height, transposition) = detect_tile_size_and_transposition(&corner_image_filename, &other_image_filename); | |
| // Determine dimensions | |
| let (width, height) = if transposition == Transposition::LeftRight { | |
| ((first_range.end - first_range.begin + 1) * tile_width, (second_range.end - second_range.begin + 1) * tile_height) | |
| } else { | |
| ((second_range.end - second_range.begin + 1) * tile_width, (first_range.end - first_range.begin + 1) * tile_height) | |
| }; | |
| let mut final_image = ImageBuffer::new(width, height); | |
| if transposition == Transposition::LeftRight { | |
| for x in first_range.iter() { | |
| for y in second_range.iter() { | |
| let image_filename = format!("{}{}{}{}{}", prefix, x, separator, y, suffix); | |
| let img = image::open(&Path::new(&image_filename)).unwrap(); | |
| assert!(img.width() == tile_width); | |
| assert!(img.height() == tile_height); | |
| assert!(final_image.copy_from(&img, (x - first_range.begin) * tile_width, (y - second_range.begin) * tile_height)); | |
| } | |
| } | |
| } else { | |
| for x in first_range.iter() { | |
| for y in second_range.iter() { | |
| let image_filename = format!("{}{}{}{}{}", prefix, x, separator, y, suffix); | |
| let img = image::open(&Path::new(&image_filename)).unwrap(); | |
| assert!(img.width() == tile_width); | |
| assert!(img.height() == tile_height); | |
| assert!(final_image.copy_from(&img, (y - second_range.begin) * tile_width, (x - first_range.begin) * tile_height)); | |
| } | |
| } | |
| } | |
| final_image.save(result_path).unwrap(); | |
| } |