direct2d 0.2.0

A safe abstraction for drawing with Direct2D
use device_context::DeviceContext;
use enums::{AlphaMode, BitmapOptions};
use error::D2DResult;
use image::{GenericImage, Image};
use math::{SizeF, SizeU};
use render_target::RenderTarget;

use std::ptr;
use std::mem;

use dxgi::surface::Surface as DxgiSurface;
use dxgi::Format;
use winapi::shared::winerror::SUCCEEDED;
use winapi::um::d2d1::{ID2D1Bitmap, ID2D1Image, D2D1_BITMAP_PROPERTIES};
use winapi::um::d2d1_1::{ID2D1DeviceContext, D2D1_BITMAP_PROPERTIES1};
use winapi::um::dcommon::D2D1_PIXEL_FORMAT;
use wio::com::ComPtr;

#[derive(Clone)]
pub struct Bitmap {
    ptr: ComPtr<ID2D1Bitmap>,
}

impl Bitmap {
    #[inline]
    pub fn create<'a, R>(context: &'a R) -> BitmapBuilder<'a, R>
    where
        R: RenderTarget + 'a,
    {
        BitmapBuilder::new(context)
    }

    #[inline]
    pub fn as_generic(&self) -> GenericImage {
        unsafe {
            let ptr = self.get_raw();
            (*ptr).AddRef();
            GenericImage::from_raw(ptr as *mut _)
        }
    }

    #[inline]
    pub fn get_size(&self) -> SizeF {
        unsafe { SizeF(self.ptr.GetSize()) }
    }

    #[inline]
    pub fn get_pixel_size(&self) -> SizeU {
        unsafe { SizeU(self.ptr.GetPixelSize()) }
    }

    #[inline]
    pub fn get_dpi(&self) -> (f32, f32) {
        let mut x = 0.0;
        let mut y = 0.0;
        unsafe {
            self.ptr.GetDpi(&mut x, &mut y);
        }
        (x, y)
    }

    #[inline]
    pub unsafe fn from_ptr(ptr: ComPtr<ID2D1Bitmap>) -> Self {
        Self { ptr }
    }

    #[inline]
    pub unsafe fn get_raw(&self) -> *mut ID2D1Bitmap {
        self.ptr.as_raw()
    }

    #[inline]
    pub unsafe fn from_raw(ptr: *mut ID2D1Bitmap) -> Self {
        Bitmap {
            ptr: ComPtr::from_raw(ptr),
        }
    }
}

impl Image for Bitmap {
    #[inline]
    unsafe fn get_ptr(&self) -> *mut ID2D1Image {
        self.ptr.as_raw() as *mut _
    }
}

unsafe impl Send for Bitmap {}
unsafe impl Sync for Bitmap {}

pub struct BitmapBuilder<'a, R>
where
    R: RenderTarget + 'a,
{
    context: &'a R,
    source: Option<BitmapSource<'a>>,
    properties: D2D1_BITMAP_PROPERTIES1,
}

const DEFAULT_BITMAP_PROPS: D2D1_BITMAP_PROPERTIES1 = D2D1_BITMAP_PROPERTIES1 {
    pixelFormat: D2D1_PIXEL_FORMAT {
        format: Format::Unknown as u32,
        alphaMode: AlphaMode::Premultiplied as u32,
    },
    dpiX: 96.0,
    dpiY: 96.0,
    bitmapOptions: BitmapOptions::NONE.0,
    colorContext: ptr::null(),
};

impl<'a, R> BitmapBuilder<'a, R>
where
    R: RenderTarget + 'a,
{
    #[inline]
    pub fn new(context: &'a R) -> Self {
        BitmapBuilder {
            context,
            source: None,
            properties: DEFAULT_BITMAP_PROPS,
        }
    }

    #[inline]
    pub fn build(self) -> D2DResult<Bitmap> {
        let source = self.source.expect("An image source must be specified");
        unsafe {
            match source {
                BitmapSource::Raw {
                    size,
                    source,
                    pitch,
                } => {
                    let source = source.map(<[_]>::as_ptr).unwrap_or(ptr::null());

                    let mut ptr = ptr::null_mut();

                    let hr;
                    if self.properties.bitmapOptions == 0 && self.properties.colorContext.is_null() {
                        hr = self.context.rt().CreateBitmap(
                            size.0,
                            source as *const _,
                            pitch,
                            (&self.properties) as *const _ as *const D2D1_BITMAP_PROPERTIES,
                            &mut ptr,
                        );
                    } else {
                        let ctx = self.context.rt();
                        let tmp = ComPtr::from_raw(ctx);
                        let res = tmp.cast::<ID2D1DeviceContext>();
                        mem::forget(tmp);
                        let ctx = res.unwrap();

                        let mut ptr2 = ptr::null_mut();
                        hr = ctx.CreateBitmap(
                            size.0,
                            source as *const _,
                            pitch,
                            &self.properties,
                            &mut ptr2,
                        );
                        ptr = ptr2 as *mut _;
                    }

                    if SUCCEEDED(hr) {
                        Ok(Bitmap::from_raw(ptr as _))
                    } else {
                        Err(hr.into())
                    }
                }
                BitmapSource::Dxgi(surface, context) => {
                    let mut ptr = ptr::null_mut();
                    let hr = (*context.get_raw()).CreateBitmapFromDxgiSurface(
                        surface.get_raw(),
                        &self.properties,
                        &mut ptr,
                    );

                    if SUCCEEDED(hr) {
                        Ok(Bitmap::from_raw(ptr as _))
                    } else {
                        Err(hr.into())
                    }
                }
            }
        }
    }

    #[inline]
    pub fn with_blank_image(mut self, size: SizeU) -> Self {
        self.source = Some(BitmapSource::Raw {
            size,
            source: None,
            pitch: 0,
        });
        self
    }

    #[inline]
    pub fn with_raw_data(mut self, size: SizeU, data: &'a [u8], pitch: u32) -> Self {
        assert!(size.height as usize * pitch as usize <= data.len());
        self.source = Some(BitmapSource::Raw {
            size,
            source: Some(data),
            pitch,
        });
        self
    }

    #[inline]
    pub fn with_format(mut self, format: Format) -> Self {
        self.properties.pixelFormat.format = format as u32;
        self
    }

    #[inline]
    pub fn with_alpha_mode(mut self, alpha_mode: AlphaMode) -> Self {
        self.properties.pixelFormat.alphaMode = alpha_mode as u32;
        self
    }

    #[inline]
    pub fn with_dpi(mut self, dpi_x: f32, dpi_y: f32) -> Self {
        println!("setting DPI to: {:?}", (dpi_x, dpi_y));
        self.properties.dpiX = dpi_x;
        self.properties.dpiY = dpi_y;
        self
    }
}

impl<'a> BitmapBuilder<'a, DeviceContext> {
    #[inline]
    pub fn with_dxgi_surface(mut self, surface: &'a DxgiSurface) -> Self {
        self.source = Some(BitmapSource::Dxgi(surface, self.context));
        self
    }

    #[inline]
    pub fn with_options(mut self, options: BitmapOptions) -> Self {
        self.properties.bitmapOptions = options.0;
        self
    }
}

enum BitmapSource<'a> {
    Raw {
        size: SizeU,
        source: Option<&'a [u8]>,
        pitch: u32,
    },
    Dxgi(&'a DxgiSurface, &'a DeviceContext),
}