<commit_before>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }

        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        Self::from_bytes(context, &buf)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
        let img = image::load_from_memory(bytes)?.to_rgba8();
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img)
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        let format = gfx.color_format();

        let dl_buffer = &mut gfx.to_rgba8_buffer;
        // check if it's big enough and recreate it if not
        let size_needed = usize::from(w) * usize::from(h) * 4;
        if dl_buffer.len() != size_needed {
            *dl_buffer = gfx.factory.create_download_buffer::<u8>(size_needed)?;
        }

        let encoder = &mut gfx.encoder;

        encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format,
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(dl_buffer)?.to_vec();
        Ok(reader)
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, .. } => param.scale(mint::Vector2 {
                x: scale.x * scale_x,
                y: scale.y * scale_y,
            }),
            Transform::Matrix(m) => param.transform(
                Matrix4::from(m) * Matrix4::from_scale(glam::vec3(scale_x, scale_y, 1.0)),
            ),
        };

        draw_image_raw(self, ctx, new_param)
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

pub(crate) fn draw_image_raw(image: &Image, ctx: &mut Context, param: DrawParam) -> GameResult {
    let gfx = &mut ctx.gfx_context;

    gfx.update_instance_properties(param)?;
    let sampler = gfx
        .samplers
        .get_or_insert(image.sampler_info, gfx.factory.as_mut());
    gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
    let typed_thingy = gfx
        .backend_spec
        .raw_to_typed_shader_resource(image.texture.clone());
    gfx.data.tex = (typed_thingy, sampler);
    let previous_mode: Option<BlendMode> = if let Some(mode) = image.blend_mode {
        let current_mode = gfx.blend_mode();
        if current_mode != mode {
            gfx.set_blend_mode(mode)?;
            Some(current_mode)
        } else {
            None
        }
    } else {
        None
    };

    gfx.draw(None)?;
    if let Some(mode) = previous_mode {
        gfx.set_blend_mode(mode)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<commit_msg>added support for the tga image format<commit_after>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }

        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let format = path
            .as_ref()
            .extension()
            .map_or_else(|| None, image::ImageFormat::from_extension);
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        if let Some(format) = format {
            Self::from_bytes_with_format(context, &buf, format)
        } else {
            // if no extension could be found try loading the image anyway using `image::load_from_memory`
            Self::from_bytes(context, &buf)
        }
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
        let img = image::load_from_memory(bytes)?.to_rgba8();
        Self::from_rgba_image(context, img)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in the given image file format.
    pub fn from_bytes_with_format(
        context: &mut Context,
        bytes: &[u8],
        format: image::ImageFormat,
    ) -> GameResult<Self> {
        let img = image::load_from_memory_with_format(bytes, format)?.to_rgba8();
        Self::from_rgba_image(context, img)
    }

    fn from_rgba_image(context: &mut Context, img: image::RgbaImage) -> GameResult<Self> {
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img)
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        let format = gfx.color_format();

        let dl_buffer = &mut gfx.to_rgba8_buffer;
        // check if it's big enough and recreate it if not
        let size_needed = usize::from(w) * usize::from(h) * 4;
        if dl_buffer.len() != size_needed {
            *dl_buffer = gfx.factory.create_download_buffer::<u8>(size_needed)?;
        }

        let encoder = &mut gfx.encoder;

        encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format,
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(dl_buffer)?.to_vec();
        Ok(reader)
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, .. } => param.scale(mint::Vector2 {
                x: scale.x * scale_x,
                y: scale.y * scale_y,
            }),
            Transform::Matrix(m) => param.transform(
                Matrix4::from(m) * Matrix4::from_scale(glam::vec3(scale_x, scale_y, 1.0)),
            ),
        };

        draw_image_raw(self, ctx, new_param)
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

pub(crate) fn draw_image_raw(image: &Image, ctx: &mut Context, param: DrawParam) -> GameResult {
    let gfx = &mut ctx.gfx_context;

    gfx.update_instance_properties(param)?;
    let sampler = gfx
        .samplers
        .get_or_insert(image.sampler_info, gfx.factory.as_mut());
    gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
    let typed_thingy = gfx
        .backend_spec
        .raw_to_typed_shader_resource(image.texture.clone());
    gfx.data.tex = (typed_thingy, sampler);
    let previous_mode: Option<BlendMode> = if let Some(mode) = image.blend_mode {
        let current_mode = gfx.blend_mode();
        if current_mode != mode {
            gfx.set_blend_mode(mode)?;
            Some(current_mode)
        } else {
            None
        }
    } else {
        None
    };

    gfx.draw(None)?;
    if let Some(mode) = previous_mode {
        gfx.set_blend_mode(mode)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<|endoftext|>


<commit_before>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        Self::from_bytes(context, &buf)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
        let img = image::load_from_memory(&bytes)?.to_rgba8();
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img)
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        // Note: In the GFX example, the download buffer is created ahead of time
        // and updated on screen resize events. This may be preferable, but then
        // the buffer also needs to be updated when we switch to/from a canvas.
        // Unsure of the performance impact of creating this as it is needed.
        // Probably okay for now though, since this probably won't be a super
        // common operation.
        let dl_buffer = gfx
            .factory
            .create_download_buffer::<[u8; 4]>(usize::from(w) * usize::from(h))?;

        let factory = &mut *gfx.factory;
        let mut local_encoder = GlBackendSpec::encoder(factory);

        local_encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format: gfx.color_format(),
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        local_encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(&dl_buffer)?;

        // intermediary buffer to avoid casting.
        // TODO: This can overflow on 32-bit platforms
        let mut data = Vec::with_capacity(usize::from(self.width) * usize::from(self.height) * 4);
        // Assuming OpenGL backend whose typical readback option (glReadPixels) has origin at bottom left.
        // Image formats on the other hand usually deal with top right.
        for y in (0..usize::from(self.height)).rev() {
            data.extend(
                reader
                    .iter()
                    .skip(y * usize::from(self.width))
                    .take(usize::from(self.width))
                    .flatten(),
            );
        }
        Ok(data)
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let gfx = &mut ctx.gfx_context;
        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, dest, .. } => {
                let new_scale = mint::Vector2 {
                    x: scale.x * scale_x,
                    y: scale.y * scale_y * -1.0,
                };
                let new_dest = mint::Point2 {
                    x: dest.x,
                    y: dest.y + f32::from(self.height()) * scale.y,
                };
                param.scale(new_scale).dest(new_dest)
            }
            Transform::Matrix(m) => {
                let m_scale = Matrix4::from_scale((scale_x, scale_y, 1.0).into());
                param.transform(Matrix4::from(m) * m_scale)
            }
        };

        gfx.update_instance_properties(new_param)?;
        let sampler = gfx
            .samplers
            .get_or_insert(self.sampler_info, gfx.factory.as_mut());
        gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
        let typed_thingy = gfx
            .backend_spec
            .raw_to_typed_shader_resource(self.texture.clone());
        gfx.data.tex = (typed_thingy, sampler);
        let previous_mode: Option<BlendMode> = if let Some(mode) = self.blend_mode {
            let current_mode = gfx.blend_mode();
            if current_mode != mode {
                gfx.set_blend_mode(mode)?;
                Some(current_mode)
            } else {
                None
            }
        } else {
            None
        };

        gfx.draw(None)?;
        if let Some(mode) = previous_mode {
            gfx.set_blend_mode(mode)?;
        }
        Ok(())
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<commit_msg>Flips images<commit_after>use std::convert::TryFrom;
use std::io::Read;
use std::path;

#[rustfmt::skip]
use ::image;

use crate::context::{Context, DebugId};
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
use crate::graphics;
use crate::graphics::shader::*;
use crate::graphics::*;

/// Generic in-GPU-memory image data available to be drawn on the screen.
/// You probably just want to look at the `Image` type.
#[derive(Clone, PartialEq)]
pub struct ImageGeneric<B>
where
    B: BackendSpec,
{
    pub(crate) texture: gfx::handle::RawShaderResourceView<B::Resources>,
    pub(crate) texture_handle: gfx::handle::RawTexture<B::Resources>,
    pub(crate) sampler_info: gfx::texture::SamplerInfo,
    pub(crate) blend_mode: Option<BlendMode>,
    pub(crate) width: u16,
    pub(crate) height: u16,

    pub(crate) debug_id: DebugId,
}

impl<B> ImageGeneric<B>
where
    B: BackendSpec,
{
    /// A helper function that just takes a factory directly so we can make an image
    /// without needing the full context object, so we can create an Image while still
    /// creating the GraphicsContext.
    pub(crate) fn make_raw(
        factory: &mut <B as BackendSpec>::Factory,
        sampler_info: &texture::SamplerInfo,
        width: u16,
        height: u16,
        rgba: &[u8],
        color_format: gfx::format::Format,
        debug_id: DebugId,
    ) -> GameResult<Self> {
        if width == 0 || height == 0 {
            let msg = format!(
                "Tried to create a texture of size {}x{}, each dimension must
                be >0",
                width, height
            );
            return Err(GameError::ResourceLoadError(msg));
        }
        // Check for overflow, which might happen on 32-bit systems.
        // Textures can be max u16*u16, pixels, but then have 4 bytes per pixel.
        let uwidth = usize::from(width);
        let uheight = usize::from(height);
        let expected_bytes = uwidth
            .checked_mul(uheight)
            .and_then(|size| size.checked_mul(4))
            .ok_or_else(|| {
                let msg = format!(
                    "Integer overflow in Image::make_raw, image size: {} {}",
                    uwidth, uheight
                );
                GameError::ResourceLoadError(msg)
            })?;
        if expected_bytes != rgba.len() {
            let msg = format!(
                "Tried to create a texture of size {}x{}, but gave {} bytes of data (expected {})",
                width,
                height,
                rgba.len(),
                expected_bytes
            );
            return Err(GameError::ResourceLoadError(msg));
        }

        // TODO: This can overflow on 32-bit platforms
        let rgba = rgba
            .chunks(4 * usize::from(width))
            .rev()
            .flatten()
            .copied()
            .collect::<Vec<u8>>();
        let rgba = rgba.as_slice();

        let kind = gfx::texture::Kind::D2(width, height, gfx::texture::AaMode::Single);
        use gfx::memory::Bind;
        let gfx::format::Format(surface_format, channel_type) = color_format;
        let texinfo = gfx::texture::Info {
            kind,
            levels: 1,
            format: surface_format,
            bind: Bind::SHADER_RESOURCE
                | Bind::RENDER_TARGET
                | Bind::TRANSFER_SRC
                | Bind::TRANSFER_DST,
            usage: gfx::memory::Usage::Dynamic,
        };
        let raw_tex = factory.create_texture_raw(
            texinfo,
            Some(channel_type),
            Some((&[rgba], gfx::texture::Mipmap::Provided)),
        )?;
        let resource_desc = gfx::texture::ResourceDesc {
            channel: channel_type,
            layer: None,
            min: 0,
            max: raw_tex.get_info().levels - 1,
            swizzle: gfx::format::Swizzle::new(),
        };
        let raw_view = factory.view_texture_as_shader_resource_raw(&raw_tex, resource_desc)?;
        Ok(Self {
            texture: raw_view,
            texture_handle: raw_tex,
            sampler_info: *sampler_info,
            blend_mode: None,
            width,
            height,
            debug_id,
        })
    }

    /// A helper function to get the raw gfx texture handle
    pub fn get_raw_texture_handle(&self) -> gfx::handle::RawTexture<B::Resources> {
        self.texture_handle.clone()
    }

    /// A helper function to get the raw gfx texture view
    pub fn get_raw_texture_view(&self) -> gfx::handle::RawShaderResourceView<B::Resources> {
        self.texture.clone()
    }
}

/// In-GPU-memory image data available to be drawn on the screen,
/// using the OpenGL backend.
///
/// Under the hood this is just an `Arc`'ed texture handle and
/// some metadata, so cloning it is fairly cheap; it doesn't
/// make another copy of the underlying image data.
pub type Image = ImageGeneric<GlBackendSpec>;

/// The supported formats for saving an image.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ImageFormat {
    /// .png image format (defaults to RGBA with 8-bit channels.)
    Png,
}

impl Image {
    /// Load a new image from the file at the given path. The documentation for the
    /// [`filesystem`](../filesystem/index.html) module explains how the path must be specified.
    pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
        let mut buf = Vec::new();
        let mut reader = context.filesystem.open(path)?;
        let _ = reader.read_to_end(&mut buf)?;
        Self::from_bytes(context, &buf)
    }

    /// Creates a new `Image` from the given buffer, which should contain an image encoded
    /// in a supported image file format.
    pub fn from_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
        let img = image::load_from_memory(&bytes)?.to_rgba8();
        let (width, height) = img.dimensions();
        let better_width = u16::try_from(width)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image width > u16::MAX")))?;
        let better_height = u16::try_from(height)
            .map_err(|_| GameError::ResourceLoadError(String::from("Image height > u16::MAX")))?;
        Self::from_rgba8(context, better_width, better_height, &img)
    }

    /// Creates a new `Image` from the given buffer of `u8` RGBA values.
    ///
    /// The pixel layout is row-major.  That is,
    /// the first 4 `u8` values make the top-left pixel in the `Image`, the
    /// next 4 make the next pixel in the same row, and so on to the end of
    /// the row.  The next `width * 4` values make up the second row, and so
    /// on.
    pub fn from_rgba8(
        context: &mut Context,
        width: u16,
        height: u16,
        rgba: &[u8],
    ) -> GameResult<Self> {
        let debug_id = DebugId::get(context);
        let color_format = context.gfx_context.color_format();
        Self::make_raw(
            &mut *context.gfx_context.factory,
            &context.gfx_context.default_sampler_info,
            width,
            height,
            rgba,
            color_format,
            debug_id,
        )
    }

    /// Dumps the `Image`'s data to a `Vec` of `u8` RGBA values.
    pub fn to_rgba8(&self, ctx: &mut Context) -> GameResult<Vec<u8>> {
        use gfx::memory::Typed;
        use gfx::traits::FactoryExt;

        let gfx = &mut ctx.gfx_context;
        let w = self.width;
        let h = self.height;

        // Note: In the GFX example, the download buffer is created ahead of time
        // and updated on screen resize events. This may be preferable, but then
        // the buffer also needs to be updated when we switch to/from a canvas.
        // Unsure of the performance impact of creating this as it is needed.
        // Probably okay for now though, since this probably won't be a super
        // common operation.
        let dl_buffer = gfx
            .factory
            .create_download_buffer::<[u8; 4]>(usize::from(w) * usize::from(h))?;

        let factory = &mut *gfx.factory;
        let mut local_encoder = GlBackendSpec::encoder(factory);

        local_encoder.copy_texture_to_buffer_raw(
            &self.texture_handle,
            None,
            gfx::texture::RawImageInfo {
                xoffset: 0,
                yoffset: 0,
                zoffset: 0,
                width: w,
                height: h,
                depth: 0,
                format: gfx.color_format(),
                mipmap: 0,
            },
            dl_buffer.raw(),
            0,
        )?;
        local_encoder.flush(&mut *gfx.device);

        let reader = gfx.factory.read_mapping(&dl_buffer)?;

        // intermediary buffer to avoid casting.
        // TODO: This can overflow on 32-bit platforms
        let mut data = Vec::with_capacity(usize::from(self.width) * usize::from(self.height) * 4);
        // Assuming OpenGL backend whose typical readback option (glReadPixels) has origin at bottom left.
        // Image formats on the other hand usually deal with top right.
        for y in (0..usize::from(self.height)).rev() {
            data.extend(
                reader
                    .iter()
                    .skip(y * usize::from(self.width))
                    .take(usize::from(self.width))
                    .flatten(),
            );
        }
        Ok(data)
    }

    /// Encode the `Image` to the given file format and
    /// write it out to the given path.
    ///
    /// See the [`filesystem`](../filesystem/index.html) module docs for where exactly
    /// the file will end up.
    pub fn encode<P: AsRef<path::Path>>(
        &self,
        ctx: &mut Context,
        format: ImageFormat,
        path: P,
    ) -> GameResult {
        use std::io;
        let data = self.to_rgba8(ctx)?;
        let f = filesystem::create(ctx, path)?;
        let writer = &mut io::BufWriter::new(f);
        let color_format = image::ColorType::Rgba8;
        match format {
            ImageFormat::Png => image::png::PngEncoder::new(writer)
                .encode(
                    &data,
                    u32::from(self.width),
                    u32::from(self.height),
                    color_format,
                )
                .map_err(Into::into),
        }
    }

    /// A little helper function that creates a new `Image` that is just
    /// a solid square of the given size and color.  Mainly useful for
    /// debugging.
    pub fn solid(context: &mut Context, size: u16, color: Color) -> GameResult<Self> {
        let (r, g, b, a) = color.into();
        let pixel_array: [u8; 4] = [r, g, b, a];
        let size_squared = usize::from(size) * usize::from(size);
        let mut buffer = Vec::with_capacity(size_squared);
        for _i in 0..size_squared {
            buffer.extend(&pixel_array[..]);
        }
        Image::from_rgba8(context, size, size, &buffer)
    }

    /// Return the width of the image.
    pub fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the image.
    pub fn height(&self) -> u16 {
        self.height
    }

    /// Get the filter mode for the image.
    pub fn filter(&self) -> FilterMode {
        self.sampler_info.filter.into()
    }

    /// Set the filter mode for the image.
    pub fn set_filter(&mut self, mode: FilterMode) {
        self.sampler_info.filter = mode.into();
    }

    /// Returns the dimensions of the image.
    pub fn dimensions(&self) -> Rect {
        Rect::new(0.0, 0.0, f32::from(self.width()), f32::from(self.height()))
    }

    /// Gets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn wrap(&self) -> (WrapMode, WrapMode) {
        (self.sampler_info.wrap_mode.0, self.sampler_info.wrap_mode.1)
    }

    /// Sets the `Image`'s `WrapMode` along the X and Y axes.
    pub fn set_wrap(&mut self, wrap_x: WrapMode, wrap_y: WrapMode) {
        self.sampler_info.wrap_mode.0 = wrap_x;
        self.sampler_info.wrap_mode.1 = wrap_y;
    }
}

impl fmt::Debug for Image {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "<Image: {}x{}, {:p}, texture address {:p}, sampler: {:?}>",
            self.width(),
            self.height(),
            self,
            &self.texture,
            &self.sampler_info
        )
    }
}

impl Drawable for Image {
    fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
        self.debug_id.assert(ctx);

        let gfx = &mut ctx.gfx_context;
        let src_width = param.src.w;
        let src_height = param.src.h;
        // We have to mess with the scale to make everything
        // be its-unit-size-in-pixels.
        let scale_x = src_width * f32::from(self.width);
        let scale_y = src_height * f32::from(self.height);
        let new_param = match param.trans {
            Transform::Values { scale, dest, .. } => {
                let new_scale = mint::Vector2 {
                    x: scale.x * scale_x,
                    y: scale.y * scale_y * -1.0,
                };
                let new_dest = mint::Point2 {
                    x: dest.x,
                    y: dest.y + f32::from(self.height()) * scale.y,
                };
                param.scale(new_scale).dest(new_dest)
            }
            Transform::Matrix(m) => {
                let m_scale = Matrix4::from_scale((scale_x, scale_y, 1.0).into());
                param.transform(Matrix4::from(m) * m_scale)
            }
        };

        gfx.update_instance_properties(new_param)?;
        let sampler = gfx
            .samplers
            .get_or_insert(self.sampler_info, gfx.factory.as_mut());
        gfx.data.vbuf = gfx.quad_vertex_buffer.clone();
        let typed_thingy = gfx
            .backend_spec
            .raw_to_typed_shader_resource(self.texture.clone());
        gfx.data.tex = (typed_thingy, sampler);
        let previous_mode: Option<BlendMode> = if let Some(mode) = self.blend_mode {
            let current_mode = gfx.blend_mode();
            if current_mode != mode {
                gfx.set_blend_mode(mode)?;
                Some(current_mode)
            } else {
                None
            }
        } else {
            None
        };

        gfx.draw(None)?;
        if let Some(mode) = previous_mode {
            gfx.set_blend_mode(mode)?;
        }
        Ok(())
    }

    fn dimensions(&self, _: &mut Context) -> Option<graphics::Rect> {
        Some(self.dimensions())
    }

    fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
        self.blend_mode = mode;
    }

    fn blend_mode(&self) -> Option<BlendMode> {
        self.blend_mode
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ContextBuilder;
    #[test]
    fn test_invalid_image_size() {
        let (ctx, _) = &mut ContextBuilder::new("unittest", "unittest").build().unwrap();
        let _i = assert!(Image::from_rgba8(ctx, 0, 0, &[]).is_err());
        let _i = assert!(Image::from_rgba8(ctx, 3432, 432, &[]).is_err());
        let _i = Image::from_rgba8(ctx, 2, 2, &[99; 16]).unwrap();
    }
}
<|endoftext|>


<commit_before>use gl;
use gl::types::*;

pub struct Framebuffer {
    gl_id: GLuint
}

impl Framebuffer {
    pub fn gl_id(&self) -> GLuint {
        self.gl_id
    }
}

impl Drop for Framebuffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteFramebuffers(1, &self.gl_id as *const GLuint);
        }
    }
}
<commit_msg>Add `Framebuffer::gen_framebuffer()`<commit_after>use gl;
use gl::types::*;
use context::Context;
use types::GLError;

pub struct Framebuffer {
    gl_id: GLuint
}

impl Framebuffer {
    pub fn gl_id(&self) -> GLuint {
        self.gl_id
    }
}

impl Drop for Framebuffer {
    fn drop(&mut self) {
        unsafe {
            gl::DeleteFramebuffers(1, &self.gl_id as *const GLuint);
        }
    }
}

impl Context {
    pub fn gen_framebuffer(&self) -> Framebuffer {
        unsafe {
            let mut id : GLuint = 0;

            gl::GenFramebuffers(1, &mut id as *mut GLuint);
            dbg_gl_sanity_check! {
                GLError::InvalidValue => "`n` is negative",
                _ => "Unknown error"
            }

            Framebuffer {
                gl_id: id
            }
        }
    }
}
<|endoftext|>


<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (words) RFILE

use crate::common::util::*;
use std::io::{Seek, SeekFrom, Write};

static FILE1: &str = "truncate_test_1";
static FILE2: &str = "truncate_test_2";

#[test]
fn test_increase_file_size() {
    let expected = 5 * 1024;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE1);
    ucmd.args(&["-s", "+5K", FILE1]).succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_increase_file_size_kb() {
    let expected = 5 * 1000;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE1);
    ucmd.args(&["-s", "+5KB", FILE1]).succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_reference() {
    let expected = 5 * 1000;
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let mut file = at.make_file(FILE2);

    // manpage: "A FILE argument that does not exist is created."
    // TODO: 'truncate' does not create the file in this case,
    //        but should because '--no-create' wasn't specified.
    at.touch(FILE1); // TODO: remove this when 'no-create' is fixed
    scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).succeeds();

    scene
        .ucmd()
        .arg("--reference")
        .arg(FILE1)
        .arg(FILE2)
        .succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_decrease_file_size() {
    let expected = 6;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size=-4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_space_in_size() {
    let expected = 4;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", " 4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_failed() {
    new_ucmd!().fails();
}

#[test]
fn test_failed_2() {
    let (_at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&[FILE1]).fails();
}

#[test]
fn test_failed_incorrect_arg() {
    let (_at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["-s", "+5A", FILE1]).fails();
}

#[test]
fn test_at_most_shrinks() {
    let expected = 4;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "<4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_most_no_change() {
    let expected = 10;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "<40", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_least_grows() {
    let expected = 15;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", ">15", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_least_no_change() {
    let expected = 10;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", ">4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_round_down() {
    let expected = 8;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "/4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_round_up() {
    let expected = 12;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "%4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_size_and_reference() {
    let expected = 15;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file1 = at.make_file(FILE1);
    let mut file2 = at.make_file(FILE2);
    file1.write_all(b"1234567890").unwrap();
    ucmd.args(&["--reference", FILE1, "--size", "+5", FILE2])
        .succeeds();
    file2.seek(SeekFrom::End(0)).unwrap();
    let actual = file2.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_error_filename_only() {
    // truncate: you must specify either '--size' or '--reference'
    new_ucmd!()
        .args(&["file"])
        .fails()
        .code_is(1)
        .stderr_contains(
            "error: The following required arguments were not provided:
    --reference <RFILE>
    --size <SIZE>",
        );
}

#[test]
fn test_invalid_option() {
    // truncate: cli parsing error returns 1
    new_ucmd!()
        .args(&["--this-arg-does-not-exist"])
        .fails()
        .code_is(1);
}

#[test]
fn test_invalid_numbers() {
    new_ucmd!()
        .args(&["-s", "0X", "file"])
        .fails()
        .stderr_contains("Invalid number: '0X'");
    new_ucmd!()
        .args(&["-s", "0XB", "file"])
        .fails()
        .stderr_contains("Invalid number: '0XB'");
    new_ucmd!()
        .args(&["-s", "0B", "file"])
        .fails()
        .stderr_contains("Invalid number: '0B'");
}

#[test]
fn test_reference_file_not_found() {
    new_ucmd!()
        .args(&["-r", "a", "b"])
        .fails()
        .stderr_contains("cannot stat 'a': No such file or directory");
}

#[test]
fn test_reference_with_size_file_not_found() {
    new_ucmd!()
        .args(&["-r", "a", "-s", "+1", "b"])
        .fails()
        .stderr_contains("cannot stat 'a': No such file or directory");
}

#[test]
fn test_truncate_bytes_size() {
    // TODO: this should succeed without error, uncomment when '--no-create' is fixed
    // new_ucmd!()
    //     .args(&["--no-create", "--size", "K", "file"])
    //     .succeeds();
    new_ucmd!()
        .args(&["--size", "1024R", "file"])
        .fails()
        .code_is(1)
        .stderr_only("truncate: Invalid number: '1024R'");
    #[cfg(not(target_pointer_width = "128"))]
    new_ucmd!()
        .args(&["--size", "1Y", "file"])
        .fails()
        .code_is(1)
        .stderr_only("truncate: Invalid number: '1Y': Value too large for defined data type");
    #[cfg(target_pointer_width = "32")]
    {
        let sizes = ["1000G", "10T"];
        for size in &sizes {
            new_ucmd!().args(&["--size", size, "file"]).succeeds();
        }
    }
}

/// Test that truncating a non-existent file creates that file.
#[test]
fn test_new_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "new_file_that_does_not_exist_yet";
    ucmd.args(&["-s", "8", filename])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(at.file_exists(filename));
    assert_eq!(at.read_bytes(filename), vec![b'\0'; 8]);
}

/// Test for not creating a non-existent file.
#[test]
fn test_new_file_no_create() {
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "new_file_that_does_not_exist_yet";
    ucmd.args(&["-s", "8", "-c", filename])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(!at.file_exists(filename));
}

#[test]
fn test_division_by_zero_size_only() {
    new_ucmd!()
        .args(&["-s", "/0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
    new_ucmd!()
        .args(&["-s", "%0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
}

#[test]
fn test_division_by_zero_reference_and_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.make_file(FILE1);
    ucmd.args(&["-r", FILE1, "-s", "/0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");

    let (at, mut ucmd) = at_and_ucmd!();
    at.make_file(FILE1);
    ucmd.args(&["-r", FILE1, "-s", "%0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
}

#[test]
fn test_no_such_dir() {
    new_ucmd!()
        .args(&["-s", "0", "a/b"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'a/b' for writing: No such file or directory");
}

/// Test that truncate with a relative size less than 0 is not an error.
#[test]
fn test_underflow_relative_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["-s-1", FILE1])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(at.file_exists(FILE1));
    assert!(at.read_bytes(FILE1).is_empty());
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_size_only() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    ucmd.args(&["-s", "0", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_reference_file_only() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    at.make_file("reference_file");
    ucmd.args(&["-r", "reference_file", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_reference_and_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    at.make_file("reference_file");
    ucmd.args(&["-r", "reference_file", "-s", "+0", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}
<commit_msg>Remove impractical test creating a file too large<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * For the full copyright and license information, please view the LICENSE
//  * file that was distributed with this source code.

// spell-checker:ignore (words) RFILE

use crate::common::util::*;
use std::io::{Seek, SeekFrom, Write};

static FILE1: &str = "truncate_test_1";
static FILE2: &str = "truncate_test_2";

#[test]
fn test_increase_file_size() {
    let expected = 5 * 1024;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE1);
    ucmd.args(&["-s", "+5K", FILE1]).succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_increase_file_size_kb() {
    let expected = 5 * 1000;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE1);
    ucmd.args(&["-s", "+5KB", FILE1]).succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_reference() {
    let expected = 5 * 1000;
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let mut file = at.make_file(FILE2);

    // manpage: "A FILE argument that does not exist is created."
    // TODO: 'truncate' does not create the file in this case,
    //        but should because '--no-create' wasn't specified.
    at.touch(FILE1); // TODO: remove this when 'no-create' is fixed
    scene.ucmd().arg("-s").arg("+5KB").arg(FILE1).succeeds();

    scene
        .ucmd()
        .arg("--reference")
        .arg(FILE1)
        .arg(FILE2)
        .succeeds();

    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_decrease_file_size() {
    let expected = 6;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size=-4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_space_in_size() {
    let expected = 4;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", " 4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_failed() {
    new_ucmd!().fails();
}

#[test]
fn test_failed_2() {
    let (_at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&[FILE1]).fails();
}

#[test]
fn test_failed_incorrect_arg() {
    let (_at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["-s", "+5A", FILE1]).fails();
}

#[test]
fn test_at_most_shrinks() {
    let expected = 4;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "<4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_most_no_change() {
    let expected = 10;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "<40", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_least_grows() {
    let expected = 15;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", ">15", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_at_least_no_change() {
    let expected = 10;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", ">4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_round_down() {
    let expected = 8;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "/4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_round_up() {
    let expected = 12;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file = at.make_file(FILE2);
    file.write_all(b"1234567890").unwrap();
    ucmd.args(&["--size", "%4", FILE2]).succeeds();
    file.seek(SeekFrom::End(0)).unwrap();
    let actual = file.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_size_and_reference() {
    let expected = 15;
    let (at, mut ucmd) = at_and_ucmd!();
    let mut file1 = at.make_file(FILE1);
    let mut file2 = at.make_file(FILE2);
    file1.write_all(b"1234567890").unwrap();
    ucmd.args(&["--reference", FILE1, "--size", "+5", FILE2])
        .succeeds();
    file2.seek(SeekFrom::End(0)).unwrap();
    let actual = file2.seek(SeekFrom::Current(0)).unwrap();
    assert!(
        expected == actual,
        "expected '{}' got '{}'",
        expected,
        actual
    );
}

#[test]
fn test_error_filename_only() {
    // truncate: you must specify either '--size' or '--reference'
    new_ucmd!()
        .args(&["file"])
        .fails()
        .code_is(1)
        .stderr_contains(
            "error: The following required arguments were not provided:
    --reference <RFILE>
    --size <SIZE>",
        );
}

#[test]
fn test_invalid_option() {
    // truncate: cli parsing error returns 1
    new_ucmd!()
        .args(&["--this-arg-does-not-exist"])
        .fails()
        .code_is(1);
}

#[test]
fn test_invalid_numbers() {
    new_ucmd!()
        .args(&["-s", "0X", "file"])
        .fails()
        .stderr_contains("Invalid number: '0X'");
    new_ucmd!()
        .args(&["-s", "0XB", "file"])
        .fails()
        .stderr_contains("Invalid number: '0XB'");
    new_ucmd!()
        .args(&["-s", "0B", "file"])
        .fails()
        .stderr_contains("Invalid number: '0B'");
}

#[test]
fn test_reference_file_not_found() {
    new_ucmd!()
        .args(&["-r", "a", "b"])
        .fails()
        .stderr_contains("cannot stat 'a': No such file or directory");
}

#[test]
fn test_reference_with_size_file_not_found() {
    new_ucmd!()
        .args(&["-r", "a", "-s", "+1", "b"])
        .fails()
        .stderr_contains("cannot stat 'a': No such file or directory");
}

#[test]
fn test_truncate_bytes_size() {
    // TODO: this should succeed without error, uncomment when '--no-create' is fixed
    // new_ucmd!()
    //     .args(&["--no-create", "--size", "K", "file"])
    //     .succeeds();
    new_ucmd!()
        .args(&["--size", "1024R", "file"])
        .fails()
        .code_is(1)
        .stderr_only("truncate: Invalid number: '1024R'");
    #[cfg(not(target_pointer_width = "128"))]
    new_ucmd!()
        .args(&["--size", "1Y", "file"])
        .fails()
        .code_is(1)
        .stderr_only("truncate: Invalid number: '1Y': Value too large for defined data type");
}

/// Test that truncating a non-existent file creates that file.
#[test]
fn test_new_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "new_file_that_does_not_exist_yet";
    ucmd.args(&["-s", "8", filename])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(at.file_exists(filename));
    assert_eq!(at.read_bytes(filename), vec![b'\0'; 8]);
}

/// Test for not creating a non-existent file.
#[test]
fn test_new_file_no_create() {
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "new_file_that_does_not_exist_yet";
    ucmd.args(&["-s", "8", "-c", filename])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(!at.file_exists(filename));
}

#[test]
fn test_division_by_zero_size_only() {
    new_ucmd!()
        .args(&["-s", "/0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
    new_ucmd!()
        .args(&["-s", "%0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
}

#[test]
fn test_division_by_zero_reference_and_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.make_file(FILE1);
    ucmd.args(&["-r", FILE1, "-s", "/0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");

    let (at, mut ucmd) = at_and_ucmd!();
    at.make_file(FILE1);
    ucmd.args(&["-r", FILE1, "-s", "%0", "file"])
        .fails()
        .no_stdout()
        .stderr_contains("division by zero");
}

#[test]
fn test_no_such_dir() {
    new_ucmd!()
        .args(&["-s", "0", "a/b"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'a/b' for writing: No such file or directory");
}

/// Test that truncate with a relative size less than 0 is not an error.
#[test]
fn test_underflow_relative_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["-s-1", FILE1])
        .succeeds()
        .no_stdout()
        .no_stderr();
    assert!(at.file_exists(FILE1));
    assert!(at.read_bytes(FILE1).is_empty());
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_size_only() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    ucmd.args(&["-s", "0", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_reference_file_only() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    at.make_file("reference_file");
    ucmd.args(&["-r", "reference_file", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}

#[cfg(not(windows))]
#[test]
fn test_fifo_error_reference_and_size() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkfifo("fifo");
    at.make_file("reference_file");
    ucmd.args(&["-r", "reference_file", "-s", "+0", "fifo"])
        .fails()
        .no_stdout()
        .stderr_contains("cannot open 'fifo' for writing: No such device or address");
}
<|endoftext|>