Page MenuHomePhorge

No OneTemporary

Size
25 KB
Referenced Files
None
Subscribers
None
This document is not UTF8. It was detected as ISO-8859-1 (Latin 1) and converted to UTF8 for display.
diff --git a/.credo.exs b/.credo.exs
new file mode 100644
index 0000000..ec6da94
--- /dev/null
+++ b/.credo.exs
@@ -0,0 +1,164 @@
+# This file contains the configuration for Credo and you are probably reading
+# this after creating it with `mix credo.gen.config`.
+#
+# If you find anything wrong or unclear in this file, please report an
+# issue on GitHub: https://github.com/rrrene/credo/issues
+#
+%{
+ #
+ # You can have as many configs as you like in the `configs:` field.
+ configs: [
+ %{
+ #
+ # Run any exec using `mix credo -C <name>`. If no exec name is given
+ # "default" is used.
+ #
+ name: "default",
+ #
+ # These are the files included in the analysis:
+ files: %{
+ #
+ # You can give explicit globs or simply directories.
+ # In the latter case `**/*.{ex,exs}` will be used.
+ #
+ included: ["lib/", "src/", "test/", "web/", "apps/"],
+ excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/", ~r"test/samples/"]
+ },
+ #
+ # Load and configure plugins here:
+ #
+ plugins: [],
+ #
+ # If you create your own checks, you must specify the source files for
+ # them here, so they can be loaded by Credo before running the analysis.
+ #
+ requires: [],
+ #
+ # If you want to enforce a style guide and need a more traditional linting
+ # experience, you can change `strict` to `true` below:
+ #
+ strict: true,
+ #
+ # If you want to use uncolored output by default, you can change `color`
+ # to `false` below:
+ #
+ color: true,
+ #
+ # You can customize the parameters of any check by adding a second element
+ # to the tuple.
+ #
+ # To disable a check put `false` as second element:
+ #
+ # {Credo.Check.Design.DuplicatedCode, false}
+ #
+ checks: [
+ #
+ ## Consistency Checks
+ #
+ {Credo.Check.Consistency.ExceptionNames, []},
+ {Credo.Check.Consistency.LineEndings, []},
+ {Credo.Check.Consistency.ParameterPatternMatching, []},
+ {Credo.Check.Consistency.SpaceAroundOperators, []},
+ {Credo.Check.Consistency.SpaceInParentheses, []},
+ {Credo.Check.Consistency.TabsOrSpaces, []},
+
+ #
+ ## Design Checks
+ #
+ # You can customize the priority of any check
+ # Priority values are: `low, normal, high, higher`
+ #
+ {Credo.Check.Design.AliasUsage,
+ [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
+ # You can also customize the exit_status of each check.
+ # If you don't want TODO comments to cause `mix credo` to fail, just
+ # set this value to 0 (zero).
+ #
+ {Credo.Check.Design.TagTODO, [exit_status: 2]},
+ {Credo.Check.Design.TagFIXME, []},
+
+ #
+ ## Readability Checks
+ #
+ {Credo.Check.Readability.AliasOrder, []},
+ {Credo.Check.Readability.FunctionNames, []},
+ {Credo.Check.Readability.LargeNumbers, []},
+ {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
+ {Credo.Check.Readability.ModuleAttributeNames, []},
+ {Credo.Check.Readability.ModuleDoc, []},
+ {Credo.Check.Readability.ModuleNames, []},
+ {Credo.Check.Readability.ParenthesesInCondition, []},
+ {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
+ {Credo.Check.Readability.PredicateFunctionNames, []},
+ {Credo.Check.Readability.PreferImplicitTry, []},
+ {Credo.Check.Readability.RedundantBlankLines, []},
+ {Credo.Check.Readability.Semicolons, []},
+ {Credo.Check.Readability.SpaceAfterCommas, []},
+ {Credo.Check.Readability.StringSigils, []},
+ {Credo.Check.Readability.TrailingBlankLine, []},
+ {Credo.Check.Readability.TrailingWhiteSpace, []},
+ # TODO: enable by default in Credo 1.1
+ {Credo.Check.Readability.UnnecessaryAliasExpansion, false},
+ {Credo.Check.Readability.VariableNames, []},
+
+ #
+ ## Refactoring Opportunities
+ #
+ {Credo.Check.Refactor.CondStatements, []},
+ {Credo.Check.Refactor.CyclomaticComplexity, []},
+ {Credo.Check.Refactor.FunctionArity, []},
+ {Credo.Check.Refactor.LongQuoteBlocks, []},
+ {Credo.Check.Refactor.MapInto, []},
+ {Credo.Check.Refactor.MatchInCondition, []},
+ {Credo.Check.Refactor.NegatedConditionsInUnless, []},
+ {Credo.Check.Refactor.NegatedConditionsWithElse, []},
+ {Credo.Check.Refactor.Nesting, []},
+ {Credo.Check.Refactor.UnlessWithElse, []},
+ {Credo.Check.Refactor.WithClauses, []},
+
+ #
+ ## Warnings
+ #
+ {Credo.Check.Warning.BoolOperationOnSameValues, []},
+ {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
+ {Credo.Check.Warning.IExPry, []},
+ {Credo.Check.Warning.IoInspect, []},
+ {Credo.Check.Warning.LazyLogging, []},
+ {Credo.Check.Warning.OperationOnSameValues, []},
+ {Credo.Check.Warning.OperationWithConstantResult, []},
+ {Credo.Check.Warning.RaiseInsideRescue, []},
+ {Credo.Check.Warning.UnusedEnumOperation, []},
+ {Credo.Check.Warning.UnusedFileOperation, []},
+ {Credo.Check.Warning.UnusedKeywordOperation, []},
+ {Credo.Check.Warning.UnusedListOperation, []},
+ {Credo.Check.Warning.UnusedPathOperation, []},
+ {Credo.Check.Warning.UnusedRegexOperation, []},
+ {Credo.Check.Warning.UnusedStringOperation, []},
+ {Credo.Check.Warning.UnusedTupleOperation, []},
+
+ #
+ # Controversial and experimental checks (opt-in, just replace `false` with `[]`)
+ #
+ {Credo.Check.Consistency.MultiAliasImportRequireUse, false},
+ {Credo.Check.Consistency.UnusedVariableNames, false},
+ {Credo.Check.Design.DuplicatedCode, false},
+ {Credo.Check.Readability.AliasAs, false},
+ {Credo.Check.Readability.MultiAlias, false},
+ {Credo.Check.Readability.Specs, false},
+ {Credo.Check.Readability.SinglePipe, false},
+ {Credo.Check.Refactor.ABCSize, false},
+ {Credo.Check.Refactor.AppendSingleItem, false},
+ {Credo.Check.Refactor.DoubleBooleanNegation, false},
+ {Credo.Check.Refactor.ModuleDependencies, false},
+ {Credo.Check.Refactor.PipeChainStart, false},
+ {Credo.Check.Refactor.VariableRebinding, false},
+ {Credo.Check.Warning.MapGetUnsafePass, false},
+ {Credo.Check.Warning.UnsafeToAtom, false}
+
+ #
+ # Custom checks can be created using `mix credo.gen.check`.
+ #
+ ]
+ }
+ ]
+}
diff --git a/.formatter.exs b/.formatter.exs
new file mode 100644
index 0000000..d2cda26
--- /dev/null
+++ b/.formatter.exs
@@ -0,0 +1,4 @@
+# Used by "mix format"
+[
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..49c1164
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+# The directory Mix will write compiled artifacts to.
+/_build/
+
+# If you run "mix test --cover", coverage assets end up here.
+/cover/
+
+# The directory Mix downloads your dependencies sources to.
+/deps/
+
+# Where third-party dependencies like ExDoc output generated docs.
+/doc/
+
+# Ignore .fetch files in case you like to edit your project deps locally.
+/.fetch
+
+# If the VM crashes, it generates a dump, let's ignore it too.
+erl_crash.dump
+
+# Also ignore archive artifacts (built via "mix archive.build").
+*.ez
+
+# Ignore package tarball (built via "mix hex.build").
+png_info-*.tar
+
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..a580fed
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,40 @@
+image: elixir:1.9
+
+variables:
+ MIX_ENV: test
+
+cache:
+ key: ${CI_COMMIT_REF_SLUG}
+ paths:
+ - deps
+ - _build
+stages:
+ - build
+ - test
+
+before_script:
+ - mix local.hex --force
+ - mix local.rebar --force
+
+build:
+ stage: build
+ script:
+ - mix deps.get
+ - mix compile --force
+
+unit-testing:
+ stage: test
+ coverage: '/(\d+\.\d+\%) \| Total/'
+ script:
+ - mix test --trace --cover
+
+lint:
+ stage: test
+ script:
+ - mix format --check-formatted
+
+analysis:
+ stage: test
+ script:
+ - mix deps.get
+ - mix credo --strict
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5d714d3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# PngInfo
+
+**TODO: Add description**
+
+## Installation
+
+If [available in Hex](https://hex.pm/docs/publish), the package can be installed
+by adding `png_info` to your list of dependencies in `mix.exs`:
+
+```elixir
+def deps do
+ [
+ {:png_info, "~> 0.1.0"}
+ ]
+end
+```
+
+Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
+and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
+be found at [https://hexdocs.pm/png_info](https://hexdocs.pm/png_info).
+
diff --git a/lib/mix/tasks/png_info.ex b/lib/mix/tasks/png_info.ex
new file mode 100644
index 0000000..0f3a87d
--- /dev/null
+++ b/lib/mix/tasks/png_info.ex
@@ -0,0 +1,11 @@
+defmodule Mix.Tasks.PngInfo do
+ use Mix.Task
+ @moduledoc false
+
+ @impl Mix.Task
+ def run([file]) do
+ data = File.read!(file)
+ out = PngInfo.parse(data)
+ IO.inspect(out)
+ end
+end
diff --git a/lib/png_info.ex b/lib/png_info.ex
new file mode 100644
index 0000000..208c637
--- /dev/null
+++ b/lib/png_info.ex
@@ -0,0 +1,243 @@
+defmodule PngInfo do
+ @moduledoc """
+ Extracts `image/png` basic information:
+
+ * width, height
+ * color type
+ * uses alpha
+ * interlace, compression, filter and bit depth
+
+ This module can work with partial/streamed binary data, as it does not requires the whole file. Depending on the PNG file,
+ the module may need from 33 bytes to more. The `stream/1` function initiates a parser, while its return `{:continue, ...}`
+ must be used with `stream/2` with more bytes.
+ Once enough bytes has been supplied and the information parsed, `{:ok, t()}` will be returned.
+
+ """
+
+ defstruct [:width, :height, :bit_depth, :filter, :color_type, :alpha, :compression, :interlace]
+
+ @type t :: %__MODULE__{
+ width: non_neg_integer(),
+ height: non_neg_integer(),
+ bit_depth: 1 | 2 | 4 | 8 | 16,
+ filter: false | :adaptive | non_neg_integer(),
+ color_type: :greyscale | :truecolour | :indexed,
+ alpha: boolean(),
+ compression: :deflate | non_neg_integer(),
+ interlace: false | :adam7 | non_neg_integer()
+ }
+ @type stream_return ::
+ {:ok, t()} | {:continue, continuation()} | {:error, {:invalid_png, error_reason()}}
+ @opaque continuation :: {function :: atom(), args :: list(), acc :: any()}
+
+ @color_types %{
+ 0 => {:greyscale, :maybe},
+ 2 => {:truecolour, :maybe},
+ 3 => {:indexed, :maybe},
+ 4 => {:greyscale, true},
+ 6 => {:truecolour, true}
+ }
+
+ @type error_reason ::
+ :bad_magic
+ | :header_checksum_mismatch
+ | :trns_checksum_mistmatch
+ | :missing_header
+ | :incomplete_data
+ @spec parse(binary()) :: {:ok, t()} | {:error, {:invalid_png, error_reason()}}
+ @doc "Extracts `t:t()` from a complete PNG `binary`."
+ def parse(binary) do
+ case stream(binary) do
+ {:ok, _} = ok -> ok
+ {:error, _} = error -> error
+ {:continue, _} -> {:error, {:invalid_png, :incomplete_data}}
+ end
+ end
+
+ @spec stream(binary()) :: stream_return()
+ @doc "Starts a stream"
+ def stream(binary \\ <<>>) do
+ stream({:find_magic, [], <<>>}, binary)
+ end
+
+ @spec stream(continuation(), binary()) :: stream_return()
+ @doc "Continue a stream"
+ def stream(continuation, binary)
+
+ def stream({fun, _, <<>>}, <<>>) when fun != :find_magic do
+ {:error, {:invalid_png, :incomplete_data}}
+ end
+
+ def stream({fun, args, acc}, data) when is_atom(fun) and is_list(args) do
+ data = [acc <> data] ++ args
+
+ case :erlang.apply(__MODULE__, fun, data) do
+ {:continue, cont} -> {:continue, cont}
+ other -> other
+ end
+ end
+
+ @doc false
+ def find_magic(<<137, 80, 78, 71, 13, 10, 26, 10, rest::bits>>) do
+ find_chunk(rest, %__MODULE__{}, "IHDR")
+ end
+
+ def find_magic(<<_, _, _, _, _, _, _, _, _rest::bits>>) do
+ {:error, {:invalid_png, :bad_magic}}
+ end
+
+ def find_magic(binary) do
+ {:continue, {:find_magic, [], binary}}
+ end
+
+ # Find a chunk
+ @doc false
+ def find_chunk(<<size::integer-size(4)-unit(8), rest::bits>>, info, name) do
+ find_chunk_name(rest, info, name, size)
+ end
+
+ def find_chunk(<<rest::bits>>, info, name), do: {:continue, {:find_chunk, [info, name], rest}}
+
+ # CHUNK NAME
+
+ # tRNS must be before IDAT
+ @doc false
+ def find_chunk_name(<<"IDAT", _::bits>>, info, "tRNS", _size) do
+ {:ok, %__MODULE__{info | alpha: false}}
+ end
+
+ # Header must be the first chunk
+ def find_chunk_name(<<chunk_name::binary-size(4), _::bits>>, _, "IHDR", _)
+ when chunk_name != "IHDR" do
+ {:error, {:invalid_png, :missing_header}}
+ end
+
+ def find_chunk_name(<<chunk_name::binary-size(4), rest::bits>>, info, name, size) do
+ find_chunk_data(rest, info, name, size, name == chunk_name)
+ end
+
+ def find_chunk_name(<<rest::bits>>, info, name, size) do
+ {:continue, {:find_chunk_name, [info, name, size], rest}}
+ end
+
+ # CHUNK DATA
+ @doc false
+ def find_chunk_data(rest, info, name, size, found) do
+ case rest do
+ <<data::binary-size(size), rest::bits>> ->
+ find_chunk_crc(rest, info, name, size, found, data)
+
+ _ ->
+ {:continue, {:find_chunk_data, [info, name, size, found], rest}}
+ end
+ end
+
+ # CHUNK CRC
+
+ # Verify CRC if this is the chunk we wanted
+ @doc false
+ def find_chunk_crc(
+ <<crc::integer-size(4)-unit(8), rest::bits>>,
+ info,
+ "IHDR",
+ _size,
+ true,
+ data
+ ) do
+ parse_header(rest, info, {"IHDR", data, check_crc("IHDR", data, crc)})
+ end
+
+ # Verify CRC if this is the chunk we wanted, and return
+ def find_chunk_crc(
+ <<crc::integer-size(4)-unit(8), _rest::bits>>,
+ info,
+ "tRNS",
+ _size,
+ true,
+ data
+ ) do
+ if check_crc("tRNS", data, crc) do
+ {:ok, %__MODULE__{info | alpha: true}}
+ else
+ {:error, :trns_checksum_mistmatch}
+ end
+ end
+
+ def find_chunk_crc(
+ <<_crc::integer-size(4)-unit(8), rest::bits>>,
+ info,
+ name,
+ _size,
+ _found,
+ _data
+ ) do
+ find_chunk(rest, info, name)
+ end
+
+ def find_chunk_crc(rest, info, name, size, found, data) do
+ {:continue, {:find_chunk_crc, [info, name, size, found, data], rest}}
+ end
+
+ # Parse IHDR data
+ defp parse_header(
+ <<rest::bits>>,
+ info,
+ {"IHDR",
+ <<width::integer-size(4)-unit(8), height::integer-size(4)-unit(8),
+ depth::integer-size(8), color_type::integer-size(8), filter::integer-size(8),
+ compression::integer-size(8), interlace::integer-size(8)>>, true}
+ ) do
+ {color_type, alpha} = Map.get(@color_types, color_type)
+
+ compression =
+ case compression do
+ 0 -> :deflate
+ end
+
+ interlace =
+ case interlace do
+ 0 -> false
+ 1 -> :adam7
+ i -> i
+ end
+
+ filter =
+ case filter do
+ 0 -> :adaptive
+ i -> i
+ end
+
+ info = %__MODULE__{
+ info
+ | width: width,
+ height: height,
+ bit_depth: depth,
+ filter: filter,
+ color_type: color_type,
+ alpha: alpha,
+ compression: compression,
+ interlace: interlace
+ }
+
+ if alpha == :maybe do
+ find_chunk(rest, info, "tRNS")
+ else
+ {:ok, info}
+ end
+ end
+
+ defp parse_header(_, _, {"IHDR", _, false}) do
+ {:error, {:invalid_png, :header_checksum_mismatch}}
+ end
+
+ defp parse_header(_, _, _) do
+ {:error, {:invalid_png, :header_missing}}
+ end
+
+ defp check_crc(name, data, crc) do
+ name
+ |> :erlang.crc32()
+ |> :erlang.crc32(data)
+ |> Kernel.==(crc)
+ end
+end
diff --git a/mix.exs b/mix.exs
new file mode 100644
index 0000000..416bedf
--- /dev/null
+++ b/mix.exs
@@ -0,0 +1,27 @@
+defmodule PngInfo.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :png_info,
+ version: "0.1.0",
+ elixir: "~> 1.10",
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ def application do
+ [
+ extra_applications: [:logger]
+ ]
+ end
+
+ defp deps do
+ [
+ {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
+ {:ex_doc, "~> 0.21", only: :dev, runtime: false},
+ {:benchee, "~> 1.0", only: [:dev, :test]}
+ ]
+ end
+end
diff --git a/mix.lock b/mix.lock
new file mode 100644
index 0000000..6d1c899
--- /dev/null
+++ b/mix.lock
@@ -0,0 +1,12 @@
+%{
+ "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
+ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
+ "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"},
+ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"},
+ "ex_doc": {:hex, :ex_doc, "0.22.2", "03a2a58bdd2ba0d83d004507c4ee113b9c521956938298eba16e55cc4aba4a6c", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "cf60e1b3e2efe317095b6bb79651f83a2c1b3edcb4d319c421d7fcda8b3aff26"},
+ "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
+ "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
+}
diff --git a/test/images/bgan6a08.png b/test/images/bgan6a08.png
new file mode 100644
index 0000000..e608738
Binary files /dev/null and b/test/images/bgan6a08.png differ
diff --git a/test/images/bgan6a16.png b/test/images/bgan6a16.png
new file mode 100644
index 0000000..984a995
Binary files /dev/null and b/test/images/bgan6a16.png differ
diff --git a/test/images/bgyn6a16.png b/test/images/bgyn6a16.png
new file mode 100644
index 0000000..ae3e9be
Binary files /dev/null and b/test/images/bgyn6a16.png differ
diff --git a/test/images/corrupted_added_cr_bytes.png b/test/images/corrupted_added_cr_bytes.png
new file mode 100644
index 0000000..5bce9f3
Binary files /dev/null and b/test/images/corrupted_added_cr_bytes.png differ
diff --git a/test/images/corrupted_crlf_null.png b/test/images/corrupted_crlf_null.png
new file mode 100644
index 0000000..1fd104b
--- /dev/null
+++ b/test/images/corrupted_crlf_null.png
@@ -0,0 +1,13 @@
+‰PNG
+
+
+
+
+
+IHDR “áÈ)ÈIDATxœ]ÑÁ
+Â0 P*@ð¡#°
+
+#TâÈ10lPF`Ø F=•ŸÄIQâ*çÅuí”`%qk
+Hžñšˆ©ñ´€m÷Íüµàߟ э=,¸fìOK
+
+ç ÐtŽÀ(Èïä’צíF ;èPº€¯¾{xpç]9‡/p*$(ì*éyìՃ ×þÚéçè@÷C¼  cÔqž‹NÛU#„)11·.räðfä0°ägh(¥týÙÂEøÿ‰kIEND®B`‚
\ No newline at end of file
diff --git a/test/images/corrupted_empty.png b/test/images/corrupted_empty.png
new file mode 100644
index 0000000..db3a5fd
Binary files /dev/null and b/test/images/corrupted_empty.png differ
diff --git a/test/images/greyscale_trns.png b/test/images/greyscale_trns.png
new file mode 100644
index 0000000..fc80020
Binary files /dev/null and b/test/images/greyscale_trns.png differ
diff --git a/test/images/indexed_noalpha.png b/test/images/indexed_noalpha.png
new file mode 100644
index 0000000..9619930
Binary files /dev/null and b/test/images/indexed_noalpha.png differ
diff --git a/test/images/indexed_trns_null.png b/test/images/indexed_trns_null.png
new file mode 100644
index 0000000..4210d16
Binary files /dev/null and b/test/images/indexed_trns_null.png differ
diff --git a/test/images/pp0n6a08.png b/test/images/pp0n6a08.png
new file mode 100644
index 0000000..4ed7a30
Binary files /dev/null and b/test/images/pp0n6a08.png differ
diff --git a/test/images/test_greyscale_alpha.png b/test/images/test_greyscale_alpha.png
new file mode 100644
index 0000000..3e13052
Binary files /dev/null and b/test/images/test_greyscale_alpha.png differ
diff --git a/test/images/test_truecolour_alpha.png b/test/images/test_truecolour_alpha.png
new file mode 100644
index 0000000..18cd886
Binary files /dev/null and b/test/images/test_truecolour_alpha.png differ
diff --git a/test/images/tp1n3p08.png b/test/images/tp1n3p08.png
new file mode 100644
index 0000000..6c5fd6e
Binary files /dev/null and b/test/images/tp1n3p08.png differ
diff --git a/test/images/truecolour_notrns.png b/test/images/truecolour_notrns.png
new file mode 100644
index 0000000..8f2aad7
Binary files /dev/null and b/test/images/truecolour_notrns.png differ
diff --git a/test/png_info_test.exs b/test/png_info_test.exs
new file mode 100644
index 0000000..139e7ce
--- /dev/null
+++ b/test/png_info_test.exs
@@ -0,0 +1,89 @@
+defmodule PngInfoTest do
+ use ExUnit.Case
+ doctest PngInfo
+
+ test "indexed" do
+ assert {:ok, info} = fake_stream("indexed_noalpha.png")
+ assert {:ok, ^info} = PngInfo.parse(image("indexed_noalpha.png"))
+ assert info.alpha == false
+ assert info.color_type == :indexed
+ assert {info.height, info.width} == {32, 32}
+ assert info.bit_depth == 4
+ assert info.compression == :deflate
+ assert info.filter == :adaptive
+ assert info.interlace == false
+ end
+
+ test "indexed, transparency" do
+ assert {:ok, info} = fake_stream("indexed_trns_null.png")
+ assert {:ok, ^info} = PngInfo.parse(image("indexed_trns_null.png"))
+ assert info.alpha == true
+ assert info.color_type == :indexed
+ assert {info.height, info.width} == {32, 32}
+ assert info.bit_depth == 8
+ end
+
+ test "greyscale, alpha" do
+ assert {:ok, info} = fake_stream("test_greyscale_alpha.png")
+ assert {:ok, ^info} = PngInfo.parse(image("test_greyscale_alpha.png"))
+ assert info.alpha == true
+ assert info.color_type == :greyscale
+ assert {info.height, info.width} == {32, 32}
+ end
+
+ test "truecolour, no transparency" do
+ assert {:ok, info} = fake_stream("truecolour_notrns.png")
+ assert {:ok, ^info} = PngInfo.parse(image("truecolour_notrns.png"))
+ assert info.alpha == false
+ assert info.color_type == :truecolour
+ end
+
+ test "greyscale, transparency" do
+ assert {:ok, info} = fake_stream("greyscale_trns.png")
+ assert {:ok, ^info} = PngInfo.parse(image("greyscale_trns.png"))
+ assert info.alpha == true
+ end
+
+ test "corrupted, empty greyscale" do
+ assert {:error, {:invalid_png, _}} = fake_stream("corrupted_empty.png")
+ assert {:error, {:invalid_png, _}} = PngInfo.parse(image("corrupted_empty.png"))
+ end
+
+ test "corrupted, added cr bytes" do
+ assert {:error, {:invalid_png, _}} = fake_stream("corrupted_added_cr_bytes.png")
+ assert {:error, {:invalid_png, _}} = PngInfo.parse(image("corrupted_added_cr_bytes.png"))
+ end
+
+ test "corrupted, cr>lf, null removed" do
+ assert {:error, {:invalid_png, _}} = fake_stream("corrupted_crlf_null.png")
+ assert {:error, {:invalid_png, _}} = PngInfo.parse(image("corrupted_crlf_null.png"))
+ end
+
+ defp image(name) do
+ File.read!("test/images/#{name}")
+ end
+
+ defp fake_stream(image, chunk_size \\ 2) do
+ image = image(image)
+ {:continue, cont} = PngInfo.stream(<<>>)
+ fake_chunk(image, chunk_size, cont)
+ end
+
+ defp fake_chunk(binary, size, cont) do
+ s = size * 8
+
+ {data, rest} =
+ case binary do
+ <<data::bits-size(s), rest::bits>> -> {data, rest}
+ <<data::bits>> -> {data, <<>>}
+ end
+
+ case PngInfo.stream(cont, data) do
+ {:continue, cont} ->
+ fake_chunk(rest, size, cont)
+
+ other ->
+ other
+ end
+ end
+end
diff --git a/test/test_helper.exs b/test/test_helper.exs
new file mode 100644
index 0000000..869559e
--- /dev/null
+++ b/test/test_helper.exs
@@ -0,0 +1 @@
+ExUnit.start()

File Metadata

Mime Type
application/octet-stream
Expires
Mon, Nov 25, 12:04 AM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39511
Default Alt Text
(25 KB)

Event Timeline