pub(super) fn dct2d(img: Cow<Image<Luma<f32>>>) -> Image<Luma<f32>> {
    #[allow(non_snake_case)]
    let T = |img: Cow<Image<_>>| -> Image<_> {
        let (w, h) = img.as_ref().dimensions();
        if w == h {
            let mut img = img.into_owned();
            transpose_inplace(&mut img);
            return img;
        }
        transpose(img.as_ref())
    };

    let mut planner = rustdct::DctPlanner::new();
    let rows_ctx = planner.plan_dct2(img.width() as usize);
    let cols_ctx = planner.plan_dct2(img.height() as usize);
    let cap = {
        let rows_cap = rows_ctx.len() + rows_ctx.get_scratch_len();
        let cols_cap = cols_ctx.len() + cols_ctx.get_scratch_len();
        rows_cap.max(cols_cap)
    };
    let mut arena = vec![0f32; cap];

    let dct = dct1d(&T(img), cols_ctx.as_ref(), &mut arena);
    dct1d(&T(Cow::Owned(dct)), rows_ctx.as_ref(), &mut arena)
}
Computes the 2 dimensional [DCT].

[DCT]: https://en.wikipedia.org/wiki/Discrete_cosine_transform
src/image_hash/signals.rs