marmoset 0.9.4

Implementation of the card game SET
Documentation
// Copyright (C) 2017 Steve Sprang
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#![allow(clippy::cast_lossless)]

extern crate cairo;
#[macro_use]
extern crate clap;
extern crate core;

use cairo::{Context, Format, ImageSurface, Operator, Rectangle};
use std::f64::consts::FRAC_PI_2;
use std::fs::File;
use std::{io, mem};

use core::deck::cards;
use core::graphics::*;
use core::utils::clamp;

const VERSION: &str = env!("CARGO_PKG_VERSION");
const CARD_ASPECT_RATIO: f64 = 3.5 / 2.25;

fn generate_card_images(path: &str, card_width: i32, border: i32,
                        vertical: bool, scheme: ColorScheme) -> io::Result<()>
{
    let card_height = (card_width as f64 / CARD_ASPECT_RATIO).ceil() as i32;
    let card_rect = Rectangle {
        // offset by (border, border)
        x: border as f64,
        y: border as f64,
        width: card_width as f64,
        height: card_height as f64,
    };

    // add space for the border on each edge
    let mut ctx_width = card_width + border * 2;
    let mut ctx_height = card_height + border * 2;
    if vertical {
        mem::swap(&mut ctx_width, &mut ctx_height);
    }

    // create the surface and context
    let surface = ImageSurface::create(Format::ARgb32, ctx_width, ctx_height)
        .expect("Could not create surface.");
    let ctx = Context::new(&surface);
    if vertical {
        // adjust the transform to account for the vertical orientation
        ctx.rotate(FRAC_PI_2);
        ctx.translate(0.0, -ctx_width as f64);
    }

    for card in cards() {
        // completely clear the context to avoid accumulating color on
        // any edge that antialiases over the transparent background
        // (e.g. rounded card corners)
        ctx.save();
        ctx.set_operator(Operator::Clear);
        ctx.paint();
        ctx.restore();

        if border > 0 {
            ctx.rounded_rect(card_rect, card_corner_radius(card_rect));
            ctx.set_source_gray(0.0);
            // half the stroke will be covered by the card
            ctx.set_line_width(border as f64 * 2.);
            ctx.stroke();
        }

        ctx.draw_card(card, card_rect, None, scheme);

        let filename = format!("{}/{}.png", path, card.index());
        let mut image = File::create(&filename)?;

        surface.write_to_png(&mut image)
            .unwrap_or_else(|_| println!("Error writing {}", filename));
    }

    Ok(())
}

fn main() {
    let matches = clap_app!(genpng =>
        (version: VERSION)
        (about: "Generate an image for each Marmoset card.")
        (@arg DIRECTORY: +required "Sets the directory in which to place the images")
        (@arg VERTICAL: -v --("render-vertically") "Orients cards vertically")
        (@arg CLASSIC: -c --("classic-colors") "Uses classic SET colors")
        (@arg BORDER: -b --border +takes_value "Sets the border width in pixels")
        (@arg WIDTH: -w --width +takes_value "Sets the card width in pixels")
    ).get_matches();

    let path = matches.value_of("DIRECTORY").unwrap();
    let width = value_t!(matches, "WIDTH", i32).unwrap_or(350);
    let border = value_t!(matches, "BORDER", i32).unwrap_or(0);
    let render_vertically = matches.is_present("VERTICAL");
    let classic_colors = matches.is_present("CLASSIC");

    // keep values within reasonable ranges
    let width = clamp(width, (64, 6400));
    let border = clamp(border, (0, 64));
    let scheme = if classic_colors { ColorScheme::Classic } else { ColorScheme::CMYK };

    generate_card_images(&path, width, border, render_vertically, scheme)
        .unwrap_or_else(|e| println!("{}", e));
}