diff --git a/.projections.json b/.projections.json new file mode 100644 index 0000000..aa41f77 --- /dev/null +++ b/.projections.json @@ -0,0 +1,21 @@ +{ + "lib/*.ex": { + "alternate": "test/{}_test.exs", + "type": "source", + "template": [ + "defmodule {camelcase|capitalize|dot} do", + "end" + ] + }, + "test/*_test.exs": { + "alternate": "lib/{}.ex", + "type": "test", + "template": [ + "defmodule {camelcase|capitalize|dot}Test do", + " use ExUnit.Case, async: true", + "", + " alias {camelcase|capitalize|dot}", + "end" + ] + } +} diff --git a/lib/xairo/context.ex b/lib/xairo/context.ex new file mode 100644 index 0000000..f302b87 --- /dev/null +++ b/lib/xairo/context.ex @@ -0,0 +1,111 @@ +defmodule Xairo.Context do + @moduledoc false + + defstruct [:context] + + def new(%Xairo.ImageSurface{surface: surface}) do + with {:ok, context} <- Xairo.Native.context_new_from_image_surface(surface) do + %__MODULE__{ + context: context + } + end + end + + def status(%__MODULE__{context: ctx}) do + with {:ok, _} <- Xairo.Native.context_status(ctx), do: :ok + end + + def set_source_rgb(%__MODULE__{context: ctx} = this, r, g, b) do + Xairo.Native.context_set_source_rgb(ctx, r / 1, g / 1, b / 1) + this + end + + def set_source_rgba(%__MODULE__{context: ctx} = this, r, g, b, a) do + Xairo.Native.context_set_source_rgba(ctx, r / 1, g / 1, b / 1, a / 1) + this + end + + def paint(%__MODULE__{context: ctx} = this) do + with {:ok, _} <- Xairo.Native.context_paint(ctx), do: this + end + + def paint_with_alpha(%__MODULE__{context: ctx} = this, alpha) do + with {:ok, _} <- Xairo.Native.context_paint_with_alpha(ctx, alpha / 1), do: this + end + + def stroke(%__MODULE__{context: ctx} = this) do + with {:ok, _} <- Xairo.Native.context_stroke(ctx), do: this + end + + def stroke_preserve(%__MODULE__{context: ctx} = this) do + with {:ok, _} <- Xairo.Native.context_stroke_preserve(ctx), do: this + end + + def fill(%__MODULE__{context: ctx} = this) do + with {:ok, _} <- Xairo.Native.context_fill(ctx), do: this + end + + def fill_preserve(%__MODULE__{context: ctx} = this) do + with {:ok, _} <- Xairo.Native.context_fill_preserve(ctx), do: this + end + + def move_to(%__MODULE__{context: ctx} = this, x, y) do + Xairo.Native.context_move_to(ctx, x / 1, y / 1) + this + end + + def line_to(%__MODULE__{context: ctx} = this, x, y) do + Xairo.Native.context_line_to(ctx, x / 1, y / 1) + this + end + + def curve_to(%__MODULE__{context: ctx} = this, x1, y1, x2, y2, x3, y3) do + Xairo.Native.context_curve_to(ctx, x1 / 1, y1 / 1, x2 / 1, y2 / 1, x3 / 1, y3 / 1) + this + end + + def rel_move_to(%__MODULE__{context: ctx} = this, x, y) do + Xairo.Native.context_rel_move_to(ctx, x / 1, y / 1) + this + end + + def rel_line_to(%__MODULE__{context: ctx} = this, x, y) do + Xairo.Native.context_rel_line_to(ctx, x / 1, y / 1) + this + end + + def rel_curve_to(%__MODULE__{context: ctx} = this, x1, y1, x2, y2, x3, y3) do + Xairo.Native.context_rel_curve_to(ctx, x1 / 1, y1 / 1, x2 / 1, y2 / 1, x3 / 1, y3 / 1) + this + end + + def arc(%__MODULE__{context: ctx} = this, cx, cy, r, a1, a2) do + Xairo.Native.context_arc(ctx, cx / 1, cy / 1, r / 1, a1 / 1, a2 / 1) + this + end + + def arc_negative(%__MODULE__{context: ctx} = this, cx, cy, r, a1, a2) do + Xairo.Native.context_arc_negative(ctx, cx / 1, cy / 1, r / 1, a1 / 1, a2 / 1) + this + end + + def new_path(%__MODULE__{context: ctx} = this) do + Xairo.Native.context_new_path(ctx) + this + end + + def new_sub_path(%__MODULE__{context: ctx} = this) do + Xairo.Native.context_new_sub_path(ctx) + this + end + + def rectangle(%__MODULE__{context: ctx} = this, x, y, w, h) do + Xairo.Native.context_rectangle(ctx, x / 1, y / 1, w / 1, h / 1) + this + end + + def close_path(%__MODULE__{context: ctx} = this) do + Xairo.Native.context_close_path(ctx) + this + end +end diff --git a/lib/xairo/image_surface.ex b/lib/xairo/image_surface.ex new file mode 100644 index 0000000..f7cba07 --- /dev/null +++ b/lib/xairo/image_surface.ex @@ -0,0 +1,33 @@ +defmodule Xairo.ImageSurface do + @moduledoc false + + defstruct [:surface] + + def new(format, width, height) do + with {:ok, image_surface} <- Xairo.Native.image_surface_create(format, width, height) do + %__MODULE__{ + surface: image_surface + } + end + end + + def format(%__MODULE__{surface: surface}) do + Xairo.Native.image_surface_format(surface) + end + + def width(%__MODULE__{surface: surface}) do + Xairo.Native.image_surface_width(surface) + end + + def height(%__MODULE__{surface: surface}) do + Xairo.Native.image_surface_height(surface) + end + + def stride(%__MODULE__{surface: surface}) do + Xairo.Native.image_surface_stride(surface) + end + + def write_to_png(%__MODULE__{surface: surface}, filename) do + Xairo.Native.image_surface_write_to_png(surface, filename) + end +end diff --git a/lib/xairo/native.ex b/lib/xairo/native.ex index bbcd3f1..be44fde 100644 --- a/lib/xairo/native.ex +++ b/lib/xairo/native.ex @@ -120,5 +120,37 @@ defmodule Xairo.Native do def set_surface_pattern_source(_i, _p), do: error() + def image_surface_create(_f, _w, _h), do: error() + def image_surface_format(_is), do: error() + def image_surface_width(_is), do: error() + def image_surface_height(_is), do: error() + def image_surface_stride(_is), do: error() + def image_surface_write_to_png(_is, _f), do: error() + + def context_new_from_image_surface(_is), do: error() + def context_status(_c), do: error() + def context_set_source_rgb(_c, _r, _g, _b), do: error() + def context_set_source_rgba(_c, _r, _g, _b, _a), do: error() + def context_stroke(_c), do: error() + def context_stroke_preserve(_c), do: error() + def context_fill(_c), do: error() + def context_fill_preserve(_c), do: error() + def context_paint(_c), do: error() + def context_paint_with_alpha(_c, _a), do: error() + def context_move_to(_c, _x, _y), do: error() + def context_line_to(_c, _x, _y), do: error() + def context_rel_move_to(_c, _x, _y), do: error() + def context_rel_line_to(_c, _x, _y), do: error() + def context_rectangle(_c, _x, _y, _w, _h), do: error() + def context_close_path(_c), do: error() + def context_curve_to(_c, _x1, _y1, _x2, _y2, _x3, _y3), do: error() + def context_rel_curve_to(_c, _x1, _y1, _x2, _y2, _x3, _y3), do: error() + def context_arc(_c, _x, _y, _r, _a1, _a2), do: error() + def context_arc_negative(_c, _x, _y, _r, _a1, _a2), do: error() + def context_new_path(_c), do: error() + def context_new_sub_path(_c), do: error() + + def stride_for_width(_f, _w), do: error() + defp error, do: :erlang.nif_error(:nif_not_loaded) end diff --git a/lib/xairo/utilities.ex b/lib/xairo/utilities.ex new file mode 100644 index 0000000..db2b774 --- /dev/null +++ b/lib/xairo/utilities.ex @@ -0,0 +1,11 @@ +defmodule Xairo.Utilities do + @moduledoc false + + def stride_for_width(format, width) do + case Xairo.Native.stride_for_width(format, width) do + {:ok, stride} -> stride + {:error, _} = error -> error + error -> {:error, error} + end + end +end diff --git a/native/xairo/src/context.rs b/native/xairo/src/context.rs new file mode 100644 index 0000000..cd4eeeb --- /dev/null +++ b/native/xairo/src/context.rs @@ -0,0 +1,146 @@ +use crate::error::XairoError; +use crate::image_surface::ImageSurface; +use rustler::ResourceArc; + +pub struct ContextRaw { + pub context: cairo::Context, +} + +unsafe impl Send for ContextRaw {} +unsafe impl Sync for ContextRaw {} + +pub type Context = ResourceArc; + +#[rustler::nif] +fn context_new_from_image_surface(surface: ImageSurface) -> Result { + match cairo::Context::new(&surface.surface) { + Ok(context) => Ok(ResourceArc::new(ContextRaw { context })), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_status(context: Context) -> Result<(), XairoError> { + match context.context.status() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_set_source_rgb(context: Context, r: f64, g: f64, b: f64) { + context.context.set_source_rgb(r, g, b); +} + +#[rustler::nif] +fn context_set_source_rgba(context: Context, r: f64, g: f64, b: f64, a: f64) { + context.context.set_source_rgba(r, g, b, a); +} + +#[rustler::nif] +fn context_paint(context: Context) -> Result<(), XairoError> { + match context.context.paint() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_paint_with_alpha(context: Context, alpha: f64) -> Result<(), XairoError> { + match context.context.paint_with_alpha(alpha) { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_fill(context: Context) -> Result<(), XairoError> { + match context.context.fill() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_fill_preserve(context: Context) -> Result<(), XairoError> { + match context.context.fill_preserve() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_stroke(context: Context) -> Result<(), XairoError> { + match context.context.stroke() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_stroke_preserve(context: Context) -> Result<(), XairoError> { + match context.context.stroke_preserve() { + Ok(_) => Ok(()), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn context_move_to(context: Context, x: f64, y: f64) { + context.context.move_to(x, y); +} + +#[rustler::nif] +fn context_rel_move_to(context: Context, x: f64, y: f64) { + context.context.rel_move_to(x, y); +} + +#[rustler::nif] +fn context_line_to(context: Context, x: f64, y: f64) { + context.context.line_to(x, y); +} + +#[rustler::nif] +fn context_rel_line_to(context: Context, x: f64, y: f64) { + context.context.rel_line_to(x, y); +} + +#[rustler::nif] +fn context_rectangle(context: Context, x: f64, y: f64, w: f64, h: f64) { + context.context.rectangle(x, y, w, h); +} + +#[rustler::nif] +fn context_close_path(context: Context) { + context.context.close_path(); +} + +#[rustler::nif] +fn context_curve_to(context: Context, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) { + context.context.curve_to(x1, y1, x2, y2, x3, y3); +} + +#[rustler::nif] +fn context_rel_curve_to(context: Context, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) { + context.context.rel_curve_to(x1, y1, x2, y2, x3, y3); +} + +#[rustler::nif] +fn context_arc(context: Context, cx: f64, cy: f64, r: f64, a1: f64, a2: f64) { + context.context.arc(cx, cy, r, a1, a2); +} + +#[rustler::nif] +fn context_arc_negative(context: Context, cx: f64, cy: f64, r: f64, a1: f64, a2: f64) { + context.context.arc_negative(cx, cy, r, a1, a2); +} + +#[rustler::nif] +fn context_new_path(context: Context) { + context.context.new_path(); +} + +#[rustler::nif] +fn context_new_sub_path(context: Context) { + context.context.new_sub_path(); +} diff --git a/native/xairo/src/enums.rs b/native/xairo/src/enums.rs new file mode 100644 index 0000000..5399554 --- /dev/null +++ b/native/xairo/src/enums.rs @@ -0,0 +1,59 @@ +use crate::error::XairoError; +use std::convert::From; + +#[derive(Copy, Clone, Debug, NifUnitEnum)] +pub enum Format { + Invalid, + Argb32, + Rgb24, + A8, + A1, + Rgb16_565, + Rgb30, +} + +impl Format { + fn stride_for_width(self, width: u32) -> Result { + match cairo::Format::from(self).stride_for_width(width) { + Ok(stride) => Ok(stride), + Err(err) => Err(err.into()), //Err(Error::Stride), + } + } +} + +#[rustler::nif] +fn stride_for_width(format: Format, width: u32) -> Result { + if width <= i32::MAX as u32 { + format.stride_for_width(width) + } else { + Err(XairoError::InvalidStride) + } +} + +impl From for cairo::Format { + fn from(format: Format) -> Self { + match format { + Format::Invalid => cairo::Format::Invalid, + Format::Argb32 => cairo::Format::ARgb32, + Format::Rgb24 => cairo::Format::Rgb24, + Format::A8 => cairo::Format::A8, + Format::A1 => cairo::Format::A1, + Format::Rgb16_565 => cairo::Format::Rgb16_565, + Format::Rgb30 => cairo::Format::Rgb30, + } + } +} + +impl From for Format { + fn from(format: cairo::Format) -> Self { + match format { + cairo::Format::ARgb32 => Format::Argb32, + cairo::Format::Rgb24 => Format::Rgb24, + cairo::Format::A8 => Format::A8, + cairo::Format::A1 => Format::A1, + cairo::Format::Rgb16_565 => Format::Rgb16_565, + cairo::Format::Rgb30 => Format::Rgb30, + _ => Format::Invalid, + } + } +} diff --git a/native/xairo/src/error.rs b/native/xairo/src/error.rs index 49b67e2..a08704d 100644 --- a/native/xairo/src/error.rs +++ b/native/xairo/src/error.rs @@ -56,3 +56,33 @@ impl rustler::Encoder for Error { format!("{}", self).encode(env) } } + +#[derive(Copy, Clone, Debug, NifUnitEnum)] +pub enum XairoError { + WriteError, + InvalidSize, + InvalidStride, + UnknownError, +} + +impl From for cairo::Error { + fn from(error: XairoError) -> Self { + match error { + XairoError::InvalidStride => cairo::Error::InvalidStride, + XairoError::InvalidSize => cairo::Error::InvalidSize, + XairoError::WriteError => cairo::Error::WriteError, + XairoError::UnknownError => cairo::Error::LastStatus, + } + } +} + +impl From for XairoError { + fn from(error: cairo::Error) -> Self { + match error { + cairo::Error::InvalidStride => XairoError::InvalidStride, + cairo::Error::InvalidSize => XairoError::InvalidSize, + cairo::Error::WriteError => XairoError::WriteError, + _ => XairoError::UnknownError, + } + } +} diff --git a/native/xairo/src/image_surface.rs b/native/xairo/src/image_surface.rs new file mode 100644 index 0000000..3e66acc --- /dev/null +++ b/native/xairo/src/image_surface.rs @@ -0,0 +1,58 @@ +use crate::enums::Format; +use crate::error::XairoError; +use rustler::ResourceArc; +use std::fs::File; + +pub struct ImageSurfaceRaw { + pub surface: cairo::ImageSurface, +} + +unsafe impl Send for ImageSurfaceRaw {} +unsafe impl Sync for ImageSurfaceRaw {} + +pub type ImageSurface = ResourceArc; + + +#[rustler::nif] +fn image_surface_create( + format: Format, + width: i32, + height: i32, +) -> Result { + let format: cairo::Format = format.into(); + match cairo::ImageSurface::create(format, width, height) { + Ok(surface) => Ok(ResourceArc::new(ImageSurfaceRaw { surface })), + Err(err) => Err(err.into()), + } +} + +#[rustler::nif] +fn image_surface_format(surface: ImageSurface) -> Format { + surface.surface.format().into() +} + +#[rustler::nif] +fn image_surface_width(surface: ImageSurface) -> i32 { + surface.surface.width() +} + +#[rustler::nif] +fn image_surface_height(surface: ImageSurface) -> i32 { + surface.surface.height() +} + +#[rustler::nif] +fn image_surface_stride(surface: ImageSurface) -> i32 { + surface.surface.stride() +} + +#[rustler::nif] +fn image_surface_write_to_png(surface: ImageSurface, filename: String) -> Result<(), XairoError> { + match File::create(filename) { + Ok(mut file) => match surface.surface.write_to_png(&mut file) { + Ok(_) => Ok(()), + Err(_) => Err(XairoError::WriteError), + }, + Err(_) => Err(XairoError::WriteError), + } +} diff --git a/native/xairo/src/lib.rs b/native/xairo/src/lib.rs index eb86929..fa11247 100644 --- a/native/xairo/src/lib.rs +++ b/native/xairo/src/lib.rs @@ -3,9 +3,14 @@ extern crate rustler_codegen; use rustler::{Env, Term}; mod color; +mod context; +use context::ContextRaw; mod drawing; +mod enums; mod error; mod extents; +mod image_surface; +use image_surface::ImageSurfaceRaw; mod line_cap; mod line_join; mod linear_gradient; @@ -133,7 +138,39 @@ rustler::init!( path::copy_path_flat, path::append_path, path::get_tolerance, - path::set_tolerance + path::set_tolerance, + // image_surface + image_surface::image_surface_create, + image_surface::image_surface_format, + image_surface::image_surface_width, + image_surface::image_surface_height, + image_surface::image_surface_stride, + image_surface::image_surface_write_to_png, + // context + context::context_new_from_image_surface, + context::context_status, + context::context_set_source_rgb, + context::context_set_source_rgba, + context::context_paint, + context::context_paint_with_alpha, + context::context_fill, + context::context_fill_preserve, + context::context_stroke, + context::context_stroke_preserve, + context::context_move_to, + context::context_rel_move_to, + context::context_line_to, + context::context_rel_line_to, + context::context_rectangle, + context::context_close_path, + context::context_curve_to, + context::context_rel_curve_to, + context::context_arc, + context::context_arc_negative, + context::context_new_path, + context::context_new_sub_path, + // enums + enums::stride_for_width, ], load = on_load ); @@ -145,5 +182,7 @@ fn on_load(env: Env, _info: Term) -> bool { rustler::resource!(XairoLinearGradient, env); rustler::resource!(XairoRadialGradient, env); rustler::resource!(XairoSolidPattern, env); + rustler::resource!(ImageSurfaceRaw, env); + rustler::resource!(ContextRaw, env); true } diff --git a/priv/native/libxairo.so b/priv/native/libxairo.so index 30639e2..c758da1 100755 Binary files a/priv/native/libxairo.so and b/priv/native/libxairo.so differ diff --git a/test/helpers/image_helpers.ex b/test/helpers/image_helpers.ex index 76643c6..d1cc634 100644 --- a/test/helpers/image_helpers.ex +++ b/test/helpers/image_helpers.ex @@ -39,7 +39,7 @@ defmodule Xairo.Helpers.ImageHelpers do :ok = File.rm(filename) end - defp hash(file) do + def hash(file) do File.stream!(file) |> Enum.reduce(:crypto.hash_init(:sha256), fn line, acc -> :crypto.hash_update(acc, line) diff --git a/test/images/basic_drawing1.png b/test/images/basic_drawing1.png new file mode 100644 index 0000000..ec37c83 Binary files /dev/null and b/test/images/basic_drawing1.png differ diff --git a/test/images/basic_drawing2.png b/test/images/basic_drawing2.png new file mode 100644 index 0000000..6a34c94 Binary files /dev/null and b/test/images/basic_drawing2.png differ diff --git a/test/images/basic_drawing3.png b/test/images/basic_drawing3.png new file mode 100644 index 0000000..789d467 Binary files /dev/null and b/test/images/basic_drawing3.png differ diff --git a/test/images/basic_drawing4.png b/test/images/basic_drawing4.png new file mode 100644 index 0000000..92a3fd5 Binary files /dev/null and b/test/images/basic_drawing4.png differ diff --git a/test/images/image_surface.png b/test/images/image_surface.png new file mode 100644 index 0000000..f842673 Binary files /dev/null and b/test/images/image_surface.png differ diff --git a/test/xairo/context_test.exs b/test/xairo/context_test.exs new file mode 100644 index 0000000..53763ab --- /dev/null +++ b/test/xairo/context_test.exs @@ -0,0 +1,122 @@ +defmodule Xairo.ContextTest do + use ExUnit.Case, async: true + import Xairo.Helpers.ImageHelpers + + alias Xairo.{Context, ImageSurface} + + setup do + surface = ImageSurface.new(:argb32, 100, 100) + context = Context.new(surface) + + {:ok, context: context, surface: surface} + end + + describe "new/1" do + test "returns a new Context", %{context: context} do + assert is_struct(context, Context) + end + end + + describe "status/1" do + test "returns the context's status", %{context: context} do + assert Context.status(context) == :ok + end + end + + describe "basic drawing" do + test "move_to, line_to, set_source_rgb, paint, stroke, fill", %{ + context: context, + surface: surface + } do + context + |> Context.set_source_rgb(1, 1, 1) + |> Context.paint() + |> Context.set_source_rgb(1, 0, 0) + |> Context.move_to(20, 50) + |> Context.line_to(50, 60) + |> Context.stroke() + |> Context.set_source_rgb(0, 1, 1) + |> Context.move_to(50, 60) + |> Context.line_to(80, 80) + |> Context.line_to(30, 75) + |> Context.close_path() + |> Context.fill() + + ImageSurface.write_to_png(surface, "basic_drawing1.png") + + assert File.exists?("basic_drawing1.png") + + assert hash("basic_drawing1.png") == hash("test/images/basic_drawing1.png") + + :ok = File.rm("basic_drawing1.png") + end + + test "set_source_rgba, fill_preserve, stroke_preserve, paint_with_alpha, rectangle", %{ + context: context, + surface: surface + } do + context + |> Context.set_source_rgb(1, 0, 0) + |> Context.paint_with_alpha(0.5) + |> Context.set_source_rgba(0, 1, 0, 0.2) + |> Context.rectangle(20, 20, 30, 60) + |> Context.fill_preserve() + |> Context.set_source_rgba(0, 0, 1, 0.5) + |> Context.stroke_preserve() + |> Context.set_source_rgba(0, 1, 0, 0.2) + |> Context.fill() + + ImageSurface.write_to_png(surface, "basic_drawing2.png") + + assert File.exists?("basic_drawing2.png") + + assert hash("basic_drawing2.png") == hash("test/images/basic_drawing2.png") + + :ok = File.rm("basic_drawing2.png") + end + + test "arc, arc_negative, curve, new_sub_path", %{context: ctx, surface: sfc} do + ctx + |> Context.set_source_rgb(1, 1, 1) + |> Context.paint() + |> Context.set_source_rgb(0, 0, 1) + |> Context.move_to(10, 10) + |> Context.curve_to(20, 20, 50, 30, 30, 80) + |> Context.arc(60, 40, 10, 0, 3.1) + |> Context.new_sub_path() + |> Context.arc_negative(70, 80, 20, 0, 3.1) + |> Context.stroke() + + ImageSurface.write_to_png(sfc, "basic_drawing3.png") + + assert File.exists?("basic_drawing3.png") + + assert hash("basic_drawing3.png") == hash("test/images/basic_drawing3.png") + + :ok = File.rm("basic_drawing3.png") + end + + test "new_path, rel_line_to, rel_move_to, rel_curve_to", %{context: ctx, surface: sfc} do + ctx + |> Context.set_source_rgb(1, 1, 1) + |> Context.paint() + |> Context.set_source_rgb(0.5, 0.5, 1) + |> Context.move_to(10, 10) + |> Context.rel_line_to(30, 30) + |> Context.new_path() + |> Context.move_to(10, 15) + |> Context.rel_line_to(20, 25) + |> Context.rel_move_to(30, 25) + |> Context.rel_curve_to(10, 15, 20, -20, 30, 20) + |> Context.stroke() + + ImageSurface.write_to_png(sfc, "basic_drawing4.png") + + assert File.exists?("basic_drawing4.png") + + assert hash("basic_drawing4.png") == hash("test/images/basic_drawing4.png") + + :ok = File.rm("basic_drawing4.png") + end + end +end diff --git a/test/xairo/image_surface_test.exs b/test/xairo/image_surface_test.exs new file mode 100644 index 0000000..a66f29c --- /dev/null +++ b/test/xairo/image_surface_test.exs @@ -0,0 +1,55 @@ +defmodule Xairo.ImageSurfaceTest do + use ExUnit.Case, async: true + import Xairo.Helpers.ImageHelpers + + alias Xairo.ImageSurface + + setup do + surface = ImageSurface.new(:argb32, 100, 100) + + {:ok, surface: surface} + end + + describe "new/3" do + test "returns a new ImageSurface", %{surface: surface} do + assert is_struct(surface, ImageSurface) + end + + test "returns an error if the dimensions are too large" do + assert ImageSurface.new(:argb32, 100_000, 100_000) == + {:error, :invalid_size} + end + end + + describe "format/1" do + test "returns the format of the surface", %{surface: surface} do + assert ImageSurface.format(surface) == :argb32 + end + end + + describe "retrieving dimensions" do + test "width/1 returns the width", %{surface: surface} do + assert ImageSurface.width(surface) == 100 + end + + test "height/1 returns height", %{surface: surface} do + assert ImageSurface.height(surface) == 100 + end + + test "stride/1 returns stride", %{surface: surface} do + assert ImageSurface.stride(surface) == 400 + end + end + + describe "write_to_png/2" do + test "successfully writes to disk", %{surface: surface} do + ImageSurface.write_to_png(surface, "image_surface.png") + + assert File.exists?("image_surface.png") + + assert hash("image_surface.png") == hash("test/images/image_surface.png") + + :ok = File.rm("image_surface.png") + end + end +end diff --git a/test/xairo/utilities_test.exs b/test/xairo/utilities_test.exs new file mode 100644 index 0000000..514aaeb --- /dev/null +++ b/test/xairo/utilities_test.exs @@ -0,0 +1,22 @@ +defmodule Xairo.UtilitiesTest do + use ExUnit.Case, async: true + + alias Xairo.Utilities + + describe "stride_for_width/2" do + test "returns the stride for the format and width" do + assert Utilities.stride_for_width(:argb32, 100) == 400 + assert Utilities.stride_for_width(:rgb24, 100) == 400 + assert Utilities.stride_for_width(:a1, 100) == 16 + end + + test "returns an error if the format is invalid" do + assert Utilities.stride_for_width(:rgb32, 100) == {:error, :invalid_variant} + end + + test "returns an error if the width is too large" do + assert Utilities.stride_for_width(:argb32, round(:math.pow(2, 31))) == + {:error, :invalid_stride} + end + end +end