diff --git a/README.md b/README.md index bdbf597..64739bb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Crystal Image (Processing) +# [Crystal Image (Processing)](http://troy.sornson.io/cr-image/) This shard aims to provide feature rich image processing abilities, both for the purpose of image manipulation as well as feature / information extraction from those images. @@ -22,6 +22,23 @@ All sample images used are from [Unsplash](https://unsplash.com/). ## Usage +CrImage supports the formats: +* PPM +* JPEG (requires `libturbojpeg`) +* PNG (requirens `libspng`) +* WebP (requires `libwebp`) + +For the formats that require a linked library, they must be `require`d explicitly: + +```crystal +require "cr-image" +require "cr-image/jpeg" +require "cr-image/png" +require "cr-image/webp" +``` + +### Example + Assuming an image `moon.jpg` already exists Picture of moon diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index eb608af..8f0c7a4 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -1,6 +1,9 @@ require "digest" require "spectator" require "../src/cr-image" +require "../src/jpeg" +require "../src/png" +require "../src/webp" require "./helpers/**" module SpecHelper diff --git a/src/cr-image.cr b/src/cr-image.cr index 6617c8c..a7553fa 100644 --- a/src/cr-image.cr +++ b/src/cr-image.cr @@ -1 +1,15 @@ -require "./cr-image/**" +require "./cr-image/operation/*" +require "./cr-image/format/save" +require "./cr-image/format/open" + +# Native crystal image format implementations +require "./cr-image/format/ppm" + +require "./cr-image/channel_type" +require "./cr-image/color" +require "./cr-image/exception" +require "./cr-image/mask" +require "./cr-image/region" +require "./cr-image/image" +require "./cr-image/grayscale_image" +require "./cr-image/rgba_image" diff --git a/src/cr-image/format/open.cr b/src/cr-image/format/open.cr index 851a6ae..217130e 100644 --- a/src/cr-image/format/open.cr +++ b/src/cr-image/format/open.cr @@ -12,12 +12,13 @@ module CrImage::Format::Open # Reads this image from file using the provided filename. def open(filename : String) : self + {% begin %} case filename - when .ends_with?(".ppm") then File.open(filename) { |f| self.from_ppm(f) } - when .ends_with?(".jpg"), .ends_with?(".jpeg") then File.open(filename) { |f| self.from_jpeg(f) } - when .ends_with?(".webp") then File.open(filename) { |f| self.from_webp(f) } - when .ends_with?(".png") then File.open(filename) { |f| self.from_png(f) } - else raise "Unknown file extension for filename #{filename}, only support .webp, .png, .ppm, .jpg, and .jpeg" + {% for format in SUPPORTED_FORMATS %} + when .ends_with?({{format[:extension]}}) then File.open(filename) { |file| self.from_{{format[:method].id}}(file) } + {% end %} + else raise Exception.new "Unknown file extension for filename #{filename}, cr-image only supports {{CrImage::Format::SUPPORTED_FORMATS.map(&.[:extension].id).join(", ").id}}" end + {% end %} end end diff --git a/src/cr-image/format/ppm.cr b/src/cr-image/format/ppm.cr index 292c6d0..bcbd38b 100644 --- a/src/cr-image/format/ppm.cr +++ b/src/cr-image/format/ppm.cr @@ -11,6 +11,8 @@ # image.save("other_image.ppm") # ``` module CrImage::Format::PPM + {% CrImage::Format::SUPPORTED_FORMATS << {extension: ".ppm", method: "ppm"} %} + macro included # Read `image_data` as PPM encoded bytes def self.from_ppm(image_data : Bytes) : self diff --git a/src/cr-image/format/save.cr b/src/cr-image/format/save.cr index 9bf243b..4b352a7 100644 --- a/src/cr-image/format/save.cr +++ b/src/cr-image/format/save.cr @@ -9,21 +9,27 @@ # image.save("image.jpg") # ``` # See `Open` for a convenience method to read images from the filesystem -module CrImage::Format::Save - # Write this image to file using the provided filename. - # - # This method _will not_ create intermediate directory paths. This method will throw an - # error if they don't exist. - def save(filename : String) : self - File.open(filename, "w") do |file| - case filename - when .ends_with?(".ppm") then to_ppm(file) - when .ends_with?(".jpg"), .ends_with?(".jpeg") then to_jpeg(file) - when .ends_with?(".webp") then to_webp(file) - when .ends_with?(".png") then to_png(file) - else raise "Unknown file extension for filename #{filename}, only support .webp, .png, .ppm, .jpg, and .jpeg" +module CrImage::Format + SUPPORTED_FORMATS = [] of Nil + + module Save + # Write this image to file using the provided filename. + # + # This method _will not_ create intermediate directory paths. This method will throw an + # error if they don't exist. + def save(filename : String) : self + File.open(filename, "w") do |file| + {% begin %} + case filename + {% for format in SUPPORTED_FORMATS %} + when .ends_with?({{format[:extension]}}) then to_{{format[:method].id}}(file) + {% end %} + else raise Exception.new "Unknown file extension for filename #{filename}, cr-image only supports {{CrImage::Format::SUPPORTED_FORMATS.map(&.[:extension].id).join(", ").id}}" + end + {% end %} end end + self end end diff --git a/src/cr-image/image.cr b/src/cr-image/image.cr index c14a646..4b53b39 100644 --- a/src/cr-image/image.cr +++ b/src/cr-image/image.cr @@ -1,15 +1,16 @@ -require "./bindings/*" -require "./format/*" -require "./operation/*" - # Common base class for `GrayscaleImage` and `RGBAImage`. All `Image`s are readable and saveable # to the filesystem or `IO` stream. abstract class CrImage::Image + macro subsclasses_include(mod) + {% for sub in @type.subclasses %} + class ::{{sub}} + include {{mod}} + end + {% end %} + end + macro inherited - include Format::JPEG include Format::PPM - include Format::WebP - include Format::PNG include Operation::BilinearResize include Operation::BoxBlur diff --git a/src/cr-image/rgba_image.cr b/src/cr-image/rgba_image.cr index c6f69f7..413330c 100644 --- a/src/cr-image/rgba_image.cr +++ b/src/cr-image/rgba_image.cr @@ -1,8 +1,3 @@ -require "./image" -require "./bindings/*" -require "./format/*" -require "./operation/*" - # An image with red, green, blue, and alpha color channels (i.e. a color image). This image type is likely the one read from and written to file (or `IO`). class CrImage::RGBAImage < CrImage::Image property red : Array(UInt8) diff --git a/src/jpeg.cr b/src/jpeg.cr new file mode 100644 index 0000000..99fc7f6 --- /dev/null +++ b/src/jpeg.cr @@ -0,0 +1,4 @@ +require "./cr-image" +require "./lib-formats/jpeg" + +CrImage::Image.subsclasses_include(CrImage::Format::JPEG) diff --git a/src/cr-image/bindings/lib_jpeg_turbo.cr b/src/lib-formats/bindings/lib_jpeg_turbo.cr similarity index 100% rename from src/cr-image/bindings/lib_jpeg_turbo.cr rename to src/lib-formats/bindings/lib_jpeg_turbo.cr diff --git a/src/cr-image/bindings/lib_spng.cr b/src/lib-formats/bindings/lib_spng.cr similarity index 100% rename from src/cr-image/bindings/lib_spng.cr rename to src/lib-formats/bindings/lib_spng.cr diff --git a/src/cr-image/bindings/lib_webp.cr b/src/lib-formats/bindings/lib_webp.cr similarity index 100% rename from src/cr-image/bindings/lib_webp.cr rename to src/lib-formats/bindings/lib_webp.cr diff --git a/src/cr-image/format/jpeg.cr b/src/lib-formats/jpeg.cr similarity index 93% rename from src/cr-image/format/jpeg.cr rename to src/lib-formats/jpeg.cr index 5877235..34a73c6 100644 --- a/src/cr-image/format/jpeg.cr +++ b/src/lib-formats/jpeg.cr @@ -1,4 +1,4 @@ -require "../bindings/lib_jpeg_turbo" +require "./bindings/lib_jpeg_turbo" # Provides methods to read from and write to jpeg. Requires `libturbojpeg` to function. # @@ -13,6 +13,11 @@ require "../bindings/lib_jpeg_turbo" # image.save("other_image.jpg") # ``` module CrImage::Format::JPEG + {% + CrImage::Format::SUPPORTED_FORMATS << {extension: ".jpeg", method: "jpeg"} + CrImage::Format::SUPPORTED_FORMATS << {extension: ".jpg", method: "jpeg"} + %} + macro included # Read `image_data` as JPEG encoded bytes def self.from_jpeg(image_data : Bytes) : self diff --git a/src/cr-image/format/png.cr b/src/lib-formats/png.cr similarity index 96% rename from src/cr-image/format/png.cr rename to src/lib-formats/png.cr index 64945be..0c19894 100644 --- a/src/cr-image/format/png.cr +++ b/src/lib-formats/png.cr @@ -1,4 +1,4 @@ -require "../bindings/lib_spng" +require "./bindings/lib_spng" # Provides methods to read from and write to PNG. Requires `libspng` to function. # @@ -13,6 +13,8 @@ require "../bindings/lib_spng" # image.save("other_image.png") # ``` module CrImage::Format::PNG + {% CrImage::Format::SUPPORTED_FORMATS << {extension: ".png", method: "png"} %} + macro included # Read `image_data` and PNG encoded bytes def self.from_png(image_data : Bytes) : self diff --git a/src/cr-image/format/webp.cr b/src/lib-formats/webp.cr similarity index 95% rename from src/cr-image/format/webp.cr rename to src/lib-formats/webp.cr index 7c844b5..c306031 100644 --- a/src/cr-image/format/webp.cr +++ b/src/lib-formats/webp.cr @@ -1,4 +1,4 @@ -require "../bindings/lib_webp" +require "./bindings/lib_webp" # Provides methods to read from and write to WebP. Requires `libwebp` to function. # @@ -13,6 +13,8 @@ require "../bindings/lib_webp" # image.save("other_image.webp") # ``` module CrImage::Format::WebP + {% CrImage::Format::SUPPORTED_FORMATS << {extension: ".webp", method: "webp"} %} + macro included # Read `image_data` as WebP encoded bytes def self.from_webp(image_data : Bytes) : self diff --git a/src/png.cr b/src/png.cr new file mode 100644 index 0000000..c0c7a58 --- /dev/null +++ b/src/png.cr @@ -0,0 +1,4 @@ +require "./cr-image" +require "./lib-formats/png" + +CrImage::Image.subsclasses_include(CrImage::Format::PNG) diff --git a/src/webp.cr b/src/webp.cr new file mode 100644 index 0000000..e6b0a9c --- /dev/null +++ b/src/webp.cr @@ -0,0 +1,4 @@ +require "./cr-image" +require "./lib-formats/webp" + +CrImage::Image.subsclasses_include(CrImage::Format::WebP)