#[allow(dead_code)]
fn getline() -> String {
    let mut res = String::new();
    std::io::stdin().read_line(&mut res).ok();
    res
}

#[allow(unused_macros)]
macro_rules! readl {
    ($t: ty) => {
        {
            let s = getline();
            s.trim().parse::<$t>().unwrap()
        }
    };
    ($( $t: ty),+ ) => {
        {
            let s = getline();
            let mut iter = s.trim().split(' ');
            ($(iter.next().unwrap().parse::<$t>().unwrap(),)*)
        }
    };
}

#[allow(unused_macros)]
macro_rules! readlvec {
    ($t: ty) => {
        {
            let s = getline();
            let iter = s.trim().split(' ');
            iter.map(|x| x.parse().unwrap()).collect::<Vec<$t>>()
        }
    }
}

#[allow(unused_macros)]
// macro_rules! debug { ($x: expr) => { println!("{}: {:?}", stringify!($x), $x) } }
macro_rules! debug { ($x: expr) => () }

#[allow(dead_code)]
fn show<T>(iter: T) -> String
where
    T: Iterator,
    T::Item: std::fmt::Display
{
    let mut res = iter.fold(String::new(), |sum, e| sum + &format!("{} ", e));
    res.pop();
    res
}

#[allow(unused_imports)]
use std::cmp::{max, min};
#[allow(unused_imports)]
use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet};
#[allow(unused_imports)]
use std::collections::btree_map::Entry::{Occupied, Vacant};

fn max_rectangle(v: &Vec<u64>) -> u64 {
    // let mut stack =  Vec::new();
    let f = |stack: &mut Vec<_>, (ind, x)| {
        let ind = ind+1;
        let mut y = 0;
        while let Some((top_val, pre_ind)) = stack.pop()  {
            if top_val < x {
                stack.push((top_val, pre_ind));
                y = pre_ind;
                break;
            }
        }
        stack.push((x, ind));
        Some((ind-y) as u64)
    };
    let l: Vec<_> = v.iter().cloned().enumerate()
        .scan(Vec::new(), &f).collect();
    let r: Vec<_> = v.iter().cloned().rev().enumerate()
        .scan(Vec::new(), &f).collect();
    let res = v.iter().zip(l.iter().zip(r.iter().rev())).enumerate()
        .map(|(i, (&x, (&l, &r)) )| {
            if i as u64 + 1 - l == 0 {
                (l+r-1)*x
            } else {
                (l+r)*x
            }
        }).max().unwrap();
    res
}

fn naive(v: &Vec<Vec<bool>>) -> u64 {
    let n = v.len();
    let m = v[0].len();
    let mut res = 0;
    let mut mav = Vec::new();
    for mask in 0..1<<(n+m) {
        let mut v = v.clone();
        for i in 0..n {
            for j in 0..m {
                v[i][j] ^= ((mask>>i)&1) ^ ((mask>>(n+j))&1) == 1;
            }
        }
        // {
        //     v.iter().for_each(|x| debug!( show(x.iter().map(|&x| if x {1} else {0})) ));
        // }
        let mut vec = vec![0; m];
        let mut ma = 0;
        for row in &v {
            for j in 0..m {
                if row[j]  {
                    vec[j] = 0;
                } else {
                    vec[j] += 1;
                }
            }
            // debug!(vec);
            ma = max(ma, max_rectangle(&vec));
        }
        if res < ma {
            res = ma;
            mav = v;
        }
        // debug!(0);
    }
    // mav.iter().for_each(|x| println!("{}", show(x.iter().map(|&x| if x {1} else {0}))));
    res
}

fn solve(h: usize, w: usize, s: Vec<Vec<bool>>) -> u64 {
    let mut d = vec![vec![false; w+1]; h+1];
    for i in 0..h {
        for j in 0..w {
            if s[i][j] {
                d[i][j] ^= true;
                d[i+1][j] ^= true;
                d[i][j+1] ^= true;
                d[i+1][j+1] ^= true;
            }
        }
    }
    debug!(0);
    // d.iter().for_each(|x| println!("{}", show(x.iter().map(|&x| if x {1} else {0}))));
    

    let mut vec = vec![0; w];
    let mut ans = 0;
    for row in d.iter().take(h) {
        for i in 0..w {
            if row[i] {
                vec[i] = 1;
            } else {
                vec[i] += 1;
            }
        }
        ans = max(ans, max_rectangle(&vec));
    }
    max(max(h, w) as u64, ans)
}

struct Random {
    x: i32,
    y: i32,
    z: i32,
    w: i32,
    t: i32,
}

impl Random {
    fn new() -> Random {
        Random {
            x: 400,
            y: 362436069,
            z: 521288629,
            w: 886751233,
            t: 1,
        }
    }

    fn next(&mut self) -> i32 {
        self.t = self.x^(self.x << 11);
        self.x = self.y;
        self.y = self.z;
        self.z = self.w;
        self.w = (self.w ^ (self.w>>19)) ^ (self.t ^ (self.t>>8));
        self.w&0x7fffffff
    }
}


fn generater(rnd: &mut Random) -> (usize, usize, Vec<Vec<bool>>) {
    let h = (rnd.next()%5) as usize + 1;
    let w = (rnd.next()%5) as usize + 1;
    let mut s = vec![vec![false; w]; h];
    for row in &mut s {
        for e in row {
            if rnd.next()%2 == 0 {
                *e = true;
            }
        }
    }
    (h, w, s)
}

fn main() {
    use std::io::Write;
    let out = std::io::stdout();
    let mut out = std::io::BufWriter::new(out.lock());
    macro_rules! printf { ($($arg:tt)*) => (write!(out, $($arg)*).unwrap()); }
    macro_rules! printfln { () => (writeln!(out).unwrap()); ($($arg:tt)*) => (writeln!(out, $($arg)*).unwrap()); }
    
    // let mut rnd =  Random::new();
    // loop {
    //     let (h, w, d) = generater(&mut rnd);
    //     let na = naive(&d);
    //     let so = solve(h, w, d.clone());
    //     if na != so {
    //         debug!((na, so));
    //         debug!((h, w));
    //         d.iter().for_each(|x| {
    //             let line = show(x.iter().map(|&x| if x {1} else {0}));
    //             debug!(line);
    //         });
    //         break;
    //     }
    // }
    // debug!(naive(&s));
    // debug!(max_rectangle(&vec![2, 2, 2]));

    let (h, w) = readl!(usize, usize);
    let s: Vec<_> = (0..h)
        .map(|_| readl!(String)
                .chars()
                .map(|c| c == '#')
                .collect::<Vec<_>>())
        .collect();
    printfln!("{}", solve(h, w, s));

}


Measured memory_bytes: 12540