From 328bd04fb1945154c9406a95f8fcd87cb1999a83 Mon Sep 17 00:00:00 2001 From: VlasovRoman Date: Thu, 5 May 2016 20:14:14 +0700 Subject: [PATCH] Initial commit --- README.md | 22 ++ dub.json | 12 + source/rip/analysis/histogram.d | 116 +++++++++ source/rip/analysis/package.d | 5 + source/rip/concepts/channel.d | 71 ++++++ source/rip/concepts/color.d | 195 +++++++++++++++ source/rip/concepts/makers.d | 223 ++++++++++++++++++ source/rip/concepts/mathematics.d | 66 ++++++ source/rip/concepts/package.d | 9 + source/rip/concepts/ranges.d | 154 ++++++++++++ source/rip/concepts/surface.d | 155 ++++++++++++ source/rip/concepts/templates.d | 78 ++++++ source/rip/draw/ifs.d | 142 +++++++++++ source/rip/draw/lsystem.d | 155 ++++++++++++ source/rip/draw/mathgraphics.d | 84 +++++++ source/rip/draw/package.d | 9 + source/rip/draw/primitives.d | 204 ++++++++++++++++ source/rip/draw/turtle.d | 206 ++++++++++++++++ source/rip/dsp/package.d | 7 + source/rip/dsp/transforms/haar.d | 72 ++++++ source/rip/dsp/transforms/hadamard.d | 72 ++++++ source/rip/dsp/transforms/slant.d | 50 ++++ source/rip/io/dlib.d | 48 ++++ source/rip/io/file.d | 53 +++++ source/rip/io/formats/bmp.d | 45 ++++ source/rip/io/formats/jpeg.d | 45 ++++ source/rip/io/formats/package.d | 10 + source/rip/io/formats/pam.d | 123 ++++++++++ source/rip/io/formats/png.d | 45 ++++ source/rip/io/formats/ppm.d | 99 ++++++++ source/rip/io/formats/tga.d | 46 ++++ source/rip/io/interfaces.d | 77 ++++++ source/rip/io/package.d | 9 + source/rip/package.d | 11 + source/rip/processing/bitPlane.d | 102 ++++++++ source/rip/processing/colorization.d | 80 +++++++ source/rip/processing/convolution.d | 61 +++++ source/rip/processing/filters/area.d | 46 ++++ source/rip/processing/filters/box.d | 34 +++ source/rip/processing/filters/clarity.d | 44 ++++ source/rip/processing/filters/directions.d | 22 ++ source/rip/processing/filters/gaussian.d | 25 ++ source/rip/processing/filters/identity.d | 25 ++ source/rip/processing/filters/kirsch.d | 102 ++++++++ source/rip/processing/filters/laplace.d | 25 ++ source/rip/processing/filters/linear.d | 61 +++++ source/rip/processing/filters/package.d | 20 ++ source/rip/processing/filters/prewitt.d | 51 ++++ source/rip/processing/filters/roberts.d | 48 ++++ source/rip/processing/filters/robinson.d | 102 ++++++++ source/rip/processing/filters/scharr.d | 50 ++++ source/rip/processing/filters/sobel.d | 93 ++++++++ source/rip/processing/grayscale.d | 60 +++++ source/rip/processing/negative.d | 28 +++ source/rip/processing/order.d | 23 ++ source/rip/processing/orderFilters/maximum.d | 25 ++ source/rip/processing/orderFilters/median.d | 25 ++ .../rip/processing/orderFilters/middlePoint.d | 25 ++ source/rip/processing/orderFilters/minimum.d | 25 ++ .../rip/processing/orderFilters/orderFilter.d | 29 +++ source/rip/processing/orderFilters/package.d | 12 + source/rip/processing/package.d | 8 + source/rip/processing/rough.d | 41 ++++ source/rip/vision/package.d | 1 + 64 files changed, 4011 insertions(+) create mode 100644 README.md create mode 100644 dub.json create mode 100644 source/rip/analysis/histogram.d create mode 100644 source/rip/analysis/package.d create mode 100644 source/rip/concepts/channel.d create mode 100644 source/rip/concepts/color.d create mode 100644 source/rip/concepts/makers.d create mode 100644 source/rip/concepts/mathematics.d create mode 100644 source/rip/concepts/package.d create mode 100644 source/rip/concepts/ranges.d create mode 100644 source/rip/concepts/surface.d create mode 100644 source/rip/concepts/templates.d create mode 100644 source/rip/draw/ifs.d create mode 100644 source/rip/draw/lsystem.d create mode 100644 source/rip/draw/mathgraphics.d create mode 100644 source/rip/draw/package.d create mode 100644 source/rip/draw/primitives.d create mode 100644 source/rip/draw/turtle.d create mode 100644 source/rip/dsp/package.d create mode 100644 source/rip/dsp/transforms/haar.d create mode 100644 source/rip/dsp/transforms/hadamard.d create mode 100644 source/rip/dsp/transforms/slant.d create mode 100644 source/rip/io/dlib.d create mode 100644 source/rip/io/file.d create mode 100644 source/rip/io/formats/bmp.d create mode 100644 source/rip/io/formats/jpeg.d create mode 100644 source/rip/io/formats/package.d create mode 100644 source/rip/io/formats/pam.d create mode 100644 source/rip/io/formats/png.d create mode 100644 source/rip/io/formats/ppm.d create mode 100644 source/rip/io/formats/tga.d create mode 100644 source/rip/io/interfaces.d create mode 100644 source/rip/io/package.d create mode 100644 source/rip/package.d create mode 100644 source/rip/processing/bitPlane.d create mode 100644 source/rip/processing/colorization.d create mode 100644 source/rip/processing/convolution.d create mode 100644 source/rip/processing/filters/area.d create mode 100644 source/rip/processing/filters/box.d create mode 100644 source/rip/processing/filters/clarity.d create mode 100644 source/rip/processing/filters/directions.d create mode 100644 source/rip/processing/filters/gaussian.d create mode 100644 source/rip/processing/filters/identity.d create mode 100644 source/rip/processing/filters/kirsch.d create mode 100644 source/rip/processing/filters/laplace.d create mode 100644 source/rip/processing/filters/linear.d create mode 100644 source/rip/processing/filters/package.d create mode 100644 source/rip/processing/filters/prewitt.d create mode 100644 source/rip/processing/filters/roberts.d create mode 100644 source/rip/processing/filters/robinson.d create mode 100644 source/rip/processing/filters/scharr.d create mode 100644 source/rip/processing/filters/sobel.d create mode 100644 source/rip/processing/grayscale.d create mode 100644 source/rip/processing/negative.d create mode 100644 source/rip/processing/order.d create mode 100644 source/rip/processing/orderFilters/maximum.d create mode 100644 source/rip/processing/orderFilters/median.d create mode 100644 source/rip/processing/orderFilters/middlePoint.d create mode 100644 source/rip/processing/orderFilters/minimum.d create mode 100644 source/rip/processing/orderFilters/orderFilter.d create mode 100644 source/rip/processing/orderFilters/package.d create mode 100644 source/rip/processing/package.d create mode 100644 source/rip/processing/rough.d create mode 100644 source/rip/vision/package.d diff --git a/README.md b/README.md new file mode 100644 index 0000000..2096c0d --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# RIP + +RIP is a library for complex image processing. + +## License + +GPLv3 + +## How to use RIP? + +Package in your `dub.json`: +```d + { + "dependencies": { + "rip": "~>0.0.1" + } + } +``` +## Dependencies + +RIP needs dlib library for loading jpeg/png/bmp/tga files. +WARNING: image can be saved ONLY in ppm/pam/png formats. diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..3f451e5 --- /dev/null +++ b/dub.json @@ -0,0 +1,12 @@ +{ + "name": "rip", + "description": "Raster and Image processor", + "copyright": "Copyright © 2016, Oleg Baharew, Roman Vlasov", + "license": "GPL-3.0", + "authors": ["Oleg Baharev (aka aquaratixc)", "Roman Vlasov"], + "dependencies": { + "dlib": { + "version" : "*" + } + } +} diff --git a/source/rip/analysis/histogram.d b/source/rip/analysis/histogram.d new file mode 100644 index 0000000..2348095 --- /dev/null +++ b/source/rip/analysis/histogram.d @@ -0,0 +1,116 @@ +module rip.analysis.histogram; + +private { + import std.algorithm; + import std.range; + import std.math; + + import rip.concepts.color; + import rip.concepts.surface; + import rip.concepts.channel; + import rip.concepts.ranges : toSurface; +} + +struct Histogram { + uint[] data; + + const Channel channel; + + alias data this; + + this(Channel channel) { + this.channel = channel; + data = new uint[channel.getRangeSize!uint()]; + } +} + +Histogram takeHistogram(Range)(in Range pixelRange, Channel channel) +//Проверка типа на InputRange +//if(InputRange!Range) +{ + auto histogram = Histogram(channel); + + pixelRange + .getPixels() + .each!( + (color) => histogram[ channel.getIndex(color) ]++ + ); + + return histogram; +} + +//std.algorithm.max не приспособлен для диапазонов +//TODO: убрать это отсюда. Ну позязя :p +T maxValue(T)(T[] args...) { + T max = args[0]; + + foreach(value; args) { + if(value > max) + max = value; + } + + return max; +} + +Surface drawHistogram( Histogram histogram, + RGBColor background = new RGBColor(255, 255, 255), + RGBColor pencil = new RGBColor(0, 0, 0)) { + uint width = histogram.channel.getRangeSize!uint; + uint height = cast(uint) + (maxValue(histogram.data) / histogram.channel.getRangeSize!float); + + auto surface = new Surface(width, height); + + foreach(i; 0..width) { + foreach(j; 0..height) { + if(j <= histogram[i] / histogram.channel.getRangeSize!float()) { + surface[i, height - j - 1] = pencil; + } + else { + surface[i, height - j - 1] = background; + } + } + } + + return surface; +} + +auto equalizeHistogram(Histogram histogram) { + Histogram equalizedHistogram = histogram; + + uint pixelsNumber = histogram.data.reduce!"a + b"; + + auto probability = histogram + .data + .map!(( float a) => a / pixelsNumber) + .array; + + int i = 1; + foreach(ref el; probability[1..$-1]) { + el += probability[i-1]; + i++; + } + + uint size = histogram.channel.getRangeSize!uint; + + equalizedHistogram = probability.map!(a => cast(uint)(a * size)).array; + return equalizedHistogram; +} + +Surface appearHistogramToSurface(Histogram histogram, Surface sur) { + import rip.concepts; + + auto channel = histogram.channel; + + auto getNewColor(in RGBColor color) { + return channel.injectValue(color, channel.getValue(color)); + } + + return sur + //функция ниже отзеркаливает картинку по диагонали + .createFences(1,1) + .map!(a => a.front) + .map!getNewColor + //функция ниже отзеркаливает картинку по диагонали + .toSurface(sur.getWidth!size_t, sur.getHeight!size_t); +} diff --git a/source/rip/analysis/package.d b/source/rip/analysis/package.d new file mode 100644 index 0000000..9cf3f69 --- /dev/null +++ b/source/rip/analysis/package.d @@ -0,0 +1,5 @@ +module rip.analysis; + +public{ + import rip.analysis.histogram; +} diff --git a/source/rip/concepts/channel.d b/source/rip/concepts/channel.d new file mode 100644 index 0000000..f5ebd3f --- /dev/null +++ b/source/rip/concepts/channel.d @@ -0,0 +1,71 @@ +module rip.concepts.channel; +//пересмотреть концепт каналов, и в случае введения в проект, +//перенести его в concepts. + +private { + import std.algorithm; + import std.math; + + import rip.concepts.color; +} + +class Channel { + float[2] range; + uint function(in RGBColor color) getValue; + + RGBColor function( in RGBColor color, + in float value) injectValue; + + this( float rangeMin, float rangeMax, + uint function(in RGBColor color) func, + RGBColor function(in RGBColor color, in float value) colorFunc){ + + range[0] = rangeMin; + range[1] = rangeMax; + getValue = func; + injectValue = colorFunc; + } + + T getRangeSize(T)() const { + return cast(T)round(range[1] - range[0] + 1); + } +} + +enum DefaultChannels { + + Red = new Channel( + 0.0f, 255.0f, + (in color) => color.red!int, + (in color, in value) => new RGBColor( + value, + color.green!ubyte, + color.blue!ubyte) + ), + + Green = new Channel( + 0.0f, 255.0f, + (in color) => color.green!int, + (in color, in value) => new RGBColor( + color.red!ubyte, + value, + color.blue!ubyte) + ), + + Blue = new Channel( + 0.0f, 255.0f, + (in color) => color.blue!int, + (in color, in value) => new RGBColor( + color.red!ubyte, + color.green!ubyte, + value) + ), + + Grayscale = new Channel( + 0.0f, 255.0f, + //так-как для построении гистограммы требуется черно-белое изображение, + //достаточно узнать только значение одного из каналов + (in color) => color.blue!int, + (in color, in value) => new RGBColor( + value, value, value) + ) +} diff --git a/source/rip/concepts/color.d b/source/rip/concepts/color.d new file mode 100644 index 0000000..1786e4e --- /dev/null +++ b/source/rip/concepts/color.d @@ -0,0 +1,195 @@ +module rip.concepts.color; + +private +{ + import std.algorithm : clamp; + import std.math; + import std.string; + + import rip.concepts.templates; +} + +// Описание цвета пикселя +class RGBColor +{ + private + { + // Является ли бинарная операция основной ? + static bool isGeneralOperation(string op) + { + switch (op) + { + case "+", "-", "*", "/", "%", "^^": + return true; + default: + return false; + } + } + } + + protected + { + ubyte R; + ubyte G; + ubyte B; + + // Задание компонент цвета + void setChannels (T, U, V)(T red, U green, V blue) + if (allArithmetic!(T, U, V)) + { + this.R = cast(ubyte) clamp(red, 0, 255); + this.G = cast(ubyte) clamp(green, 0, 255); + this.B = cast(ubyte) clamp(blue, 0, 255); + } + } + + // Параметризованный конструктор для облегчения работы с любым арифметическим типом + this(T, U, V)(T red, U green, V blue) + if (allArithmetic!(T, U, V)) + { + setChannels(red, green, blue); + } + + // Красная компонента цвета + mixin(addTypedGetter!("R", "red")); + + // Зеленая компонента цвета + mixin(addTypedGetter!("G", "green")); + + // Синяя компонента + mixin(addTypedGetter!("B", "blue")); + + // Интенсивность цвета для человеческого глаза + mixin(addTypedGetter!("0.3f * R + 0.59f * G + 0.11f * B", "luminance")); + + // Расстояние между двумя цветами в пространстве RGB + T distance(T)(RGBColor rhs) + { + auto dRed = (this.red!float - rhs.red!float) ^^ 2; + auto dGreen = (this.green!float - rhs.green!float) ^^ 2; + auto dBlue = (this.blue!float - rhs.blue!float) ^^ 2; + + return sqrt(dRed + dGreen + dBlue); + } + + // Арифметические операции для цветов + RGBColor opBinary(string op)(auto ref RGBColor rhs) + { + RGBColor result; + + static if (isGeneralOperation(op)) + { + mixin("result = new RGBColor(" ~ + "this.red!float " ~ op ~ " rhs.red!float," ~ + "this.green!float " ~ op ~ " rhs.green!float," ~ + "this.blue!float " ~ op ~ "rhs.blue!float," ~ ");" + ); + } + else + { + mixin("result = new RGBColor(" ~ + "this.red!long " ~ op ~ " rhs.red!long," ~ + "this.green!long " ~ op ~ " rhs.green!long," ~ + "this.blue!long " ~ op ~ "rhs.green!long," ~ ");" + ); + } + + return result; + } + + // Арифметические операции с левой стороныs + void opOpAssign(string op)(auto ref RGBColor rhs) + { + mixin("setChannels( + this.red!float " ~ op ~" rhs.red!float, + this.green!float " ~ op ~" rhs.green!float, + this.blue!float " ~ op ~" rhs.blue!float);"); + } + + // Арифметические операции между цветом и арифметическим скаляром + RGBColor opBinary(string op, T)(auto ref T rhs) + if (allArithmetic!T) + { + RGBColor result; + + static if (isGeneralOperation(op)) + { + mixin("result = new RGBColor(" ~ + "this.red!float " ~ op ~ " cast(float) rhs," ~ + "this.green!float " ~ op ~ " cast(float) rhs," ~ + "this.blue!float " ~ op ~ " cast(float) rhs," ~ ");" + ); + } + else + { + mixin("result = new RGBColor(" ~ + "this.red!long " ~ op ~ " cast(long) rhs," ~ + "this.green!long " ~ op ~ " cast(long) rhs," ~ + "this.blue!long " ~ op ~ "cast(long) rhs," ~ ");" + ); + } + + return result; + } + + // Операции арифметики с правой стороны выражения + RGBColor opBinaryRight(string op, T)(auto ref T rhs) + if (allArithmetic!T) + { + RGBColor result; + + static if (isGeneralOperation(op)) + { + mixin("result = new RGBColor(" ~ + "this.red!float " ~ op ~ " cast(float) rhs," ~ + "this.green!float " ~ op ~ " cast(float) rhs," ~ + "this.blue!float " ~ op ~ " cast(float) rhs," ~ ");" + ); + } + else + { + mixin("result = new RGBColor(" ~ + "this.red!long " ~ op ~ " cast(long) rhs," ~ + "this.green!long " ~ op ~ " cast(long) rhs," ~ + "this.blue!long " ~ op ~ "cast(long) rhs," ~ ");" + ); + } + + return result; + } + + // Гамма-коррекция + RGBColor gamma(T, U)(T coefficient, U power) + if (allArithmetic!(T, U)) + { + auto red = cast(float) coefficient * ((this.red!float / 255.0) ^^ cast(float) power); + auto green = cast(float) coefficient * ((this.green!float / 255.0) ^^ cast(float) power); + auto blue = cast(float) coefficient * ((this.blue!float / 255.0) ^^ cast(float) power); + + return new RGBColor(red * 255.0, green * 255.0, blue * 255.0); + } + + // Логарифмирование + RGBColor log(T)(T base = cast(T) 2) + if (allArithmetic!(T, U)) + { + assert(base != 0); + auto red = (R == 0) ? 0 : log(this.red!float / 255.0) / log(cast(float) base); + auto green = (G == 0) ? 0 : log(this.green!float / 255.0) / log(cast(float) base); + auto blue = (B == 0) ? 0 : log(this.blue!float / 255.0) / log(cast(float) base); + + return new RGBColor(red * 255.0, green * 255.0, blue * 255.0); + } + + // Инвертирование цвета ( в качестве базиса - белый цвет) + RGBColor invert() + { + return new RGBColor(255 - R, 255 - G, 255 - B); + } + + // Строковое представление цвета + string toString() + { + return format("RGBColor(%d, %d, %d)", R, G, B); + } +} \ No newline at end of file diff --git a/source/rip/concepts/makers.d b/source/rip/concepts/makers.d new file mode 100644 index 0000000..22eff31 --- /dev/null +++ b/source/rip/concepts/makers.d @@ -0,0 +1,223 @@ +//Возможно, этому модулю подойдет другое имя +module rip.concepts.makers; + +protected { + import std.stdio; + import std.math; + + import rip.concepts.color; +} + +RGBColor makeHSV(in float h, lazy float s, lazy float v) +in { + if(!isNaN(h)) { + assert(h >= 0 && h <= 360, + "H must belong to the segment [0, 360] or be NAN"); + assert(s >= 0 && s <= 1, + "S must belong to the segment [0, 1]"); + assert(v >= 0 && v <= 1, + "V must belong to the segment [0, 1]"); + } +} +body { + if(isNaN(h)) + return new RGBColor(0, 0, 0); + else { + + float c = v * s; + float _h = h / 60; + float x = c * (1 - abs(_h % 2 - 1)); + float m = v - c; + + float _r = 0, _g = 0, _b = 0; + + if(_h >= 0 && _h < 1) { + _r = c; + _g = x; + } + else if(_h < 2) { + _r = x; + _g = c; + } + else if(_h < 3) { + _g = c; + _b = x; + } + else if(_h < 4) { + _g = x; + _b = c; + } + else if(_h < 5) { + _r = x; + _b = c; + } + else { + _r = c; + _b = x; + } + + return new RGBColor( + round((_r + m) * 255), + round((_g + m) * 255), + round((_b + m) * 255)); + } + + assert(0); +} + +//Две практически одинаковые ф-ции в модуле. Надо +//подумать об их объединении +RGBColor makeHSL(in float h, lazy float s, lazy float l) +in { + if(!isNaN(h)) { + assert(h >= 0 && h < 360, + "H must belong to the segment [0, 360) or be NAN"); + assert(s >= 0 && s <= 1, + "S must belong to the segment [0, 1]"); + assert(l >= 0 && l <= 1, + "L must belong to the segment [0, 1]"); + } +} +body { + if(isNaN(h)) + return new RGBColor(0, 0, 0); + else { + + float c = (1 - abs(2 * l - 1)) * s; + float _h = h / 60; + float x = c * (1 - abs(_h % 2 - 1)); + float m = l - c / 2; + + float _r = 0, _g = 0, _b = 0; + + if(_h >= 0 && _h < 1) { + _r = c; + _g = x; + } + else if(_h < 2) { + _r = x; + _g = c; + } + else if(_h < 3) { + _g = c; + _b = x; + } + else if(_h < 4) { + _g = x; + _b = c; + } + else if(_h < 5) { + _r = x; + _b = c; + } + else { + _r = c; + _b = x; + } + + return new RGBColor( + round((_r + m) * 255), + round((_g + m) * 255), + round((_b + m) * 255)); + } + + assert(0); +} + +RGBColor makeCMYK(in float c, in float m, in float y, in float k) +in { + assert(c >= 0 && c <= 1, + "C must belong to the segment [0, 1]"); + assert(m >= 0 && m <= 1, + "M must belong to the segment [0, 1]"); + assert(y >= 0 && y <= 1, + "Y must belong to the segment [0, 1]"); + assert(k >= 0 && k <= 1, + "K must belong to the segment [0, 1]"); +} +body { + return new RGBColor( + round(255 * (1 - c) * (1 - k)), + round(255 * (1 - m) * (1 - k)), + round(255 * (1 - y) * (1 - k))); +} + +RGBColor makeXYZ(float x, float y, float z) +in { + assert(x >= 0 && x <= 95.047f, + "x must belong to the segment [0, 95.047]"); + assert(y >= 0 && y <= 100, + "y must belong to the segment [0, 100]"); + assert(z >= 0 && z <= 108.883f, + "z must belong to the segment [0, 108.883]"); +} +body { + x /= 100.0f; + y /= 100.0f; + z /= 100.0f; + + auto r = x * 3.2406 + y * -1.5372 + z * -0.4986; + auto g = x * -0.9689 + y * 1.8758 + z * 0.0415; + auto b = x * 0.0557 + y * -0.2040 + z * 1.0570; + + r = r > 0.0031308 ? 1.055 * pow(r, 1 / 2.4) - 0.055 : 12.92 * r; + g = g > 0.0031308 ? 1.055 * pow(g, 1 / 2.4) - 0.055 : 12.92 * g; + b = b > 0.0031308 ? 1.055 * pow(b, 1 / 2.4) - 0.055 : 12.92 * b; + + return new RGBColor(r * 255.0f, g * 255.0f, b * 255.0f); +} + +RGBColor makeLAB(float l, float a, float b) +in {} +body { + const float E = 0.008856f; + const float K = 903.3f; + + auto wX = 95.047f; + auto wY = 100.0f; + auto wZ = 108.883f; + + float _y = (l + 16) / 116; + float _x = a / 500 + _y; + float _z = _y - b / 200; + + auto y3 = pow(_y, 3); + auto x3 = pow(_x, 3); + auto z3 = pow(_z, 3); + + if(y3 > E) + _y = y3; + else + _y = (_y - 16 / 116 ) / 7.787; + + if(x3 > E) + _x = x3; + else + _x = (_x - 16 / 116 ) / 7.787; + + if(z3 > E) + _z = z3; + else + _z = (_z - 16 / 116 ) / 7.787; + + _x *= wX; + _y *= wY; + _z *= wZ; + + return makeXYZ(_x, _y, _z); +} + +unittest { + auto color = makeHSV(72, 0.83, 0.73); + + assert(color.red!ubyte == 155); + assert(color.green!ubyte == 186); + assert(color.blue!ubyte == 32); + + float emptyFloat; + color = makeHSV(emptyFloat, 0.45, 0.6); + + assert(color.red!ubyte == 0); + assert(color.green!ubyte == 0); + assert(color.blue!ubyte == 0); +} diff --git a/source/rip/concepts/mathematics.d b/source/rip/concepts/mathematics.d new file mode 100644 index 0000000..e3de50e --- /dev/null +++ b/source/rip/concepts/mathematics.d @@ -0,0 +1,66 @@ +module rip.concepts.mathematics; + +private +{ + import std.math; + + import rip.concepts.templates; + + static real RADIAN_IN_DEGREES = 180.0 / PI; + static real DEGREE_IN_RADIANS = PI / 180.0; +} + +// Касинус +T cas(T)(T x) + if (allArithmetic!T) +{ + auto argument = cast(real) x; + return cast(T) SQRT2 * cos(x + PI_4); +} + + +// Ненормированный кардинальный синус +T sinc(T)(T x) + if (allArithmetic!T) +{ + auto argument = cast(real) x; + + if (approxEqual(x, 0)) + { + return 1; + } + else + { + return sin(x) / x; + } +} + +// Нгормированный кардинальный синус +T normalizedSinc(T)(T x) + if (allArithmetic!T) +{ + auto argument = cast(real) x; + + if (approxEqual(x, 0)) + { + return 1; + } + else + { + return sin(PI * x) / (PI * x); + } +} + +T degreesInRadians(T)(T x) + if (allArithmetic!T) +{ + auto argument = cast(real) x; + return x * DEGREE_IN_RADIANS; +} + +T radiansInDegrees(T)(T x) + if (allArithmetic!T) +{ + auto argument = cast(real) x; + return x * RADIAN_IN_DEGREES; +} diff --git a/source/rip/concepts/package.d b/source/rip/concepts/package.d new file mode 100644 index 0000000..5570770 --- /dev/null +++ b/source/rip/concepts/package.d @@ -0,0 +1,9 @@ +module rip.concepts; + +public { + import rip.concepts.color; + import rip.concepts.mathematics; + import rip.concepts.ranges; + import rip.concepts.surface; + import rip.concepts.templates; +} diff --git a/source/rip/concepts/ranges.d b/source/rip/concepts/ranges.d new file mode 100644 index 0000000..bc1cefc --- /dev/null +++ b/source/rip/concepts/ranges.d @@ -0,0 +1,154 @@ +module rip.concepts.ranges; + +private +{ + import std.algorithm; + import std.array; + import std.range; + + import rip.concepts.color; + import rip.concepts.surface; + import rip.concepts.templates; +} + +Surface toSurface(Range)(Range r, size_t width, size_t height) + if (is(ElementType!Range == RGBColor)) +{ + Surface surface = new Surface(width, height); + auto imageArray = r.array; + + foreach (i; 0 .. width) + { + foreach (j; 0 .. height) + { + surface[i, j] = imageArray[j * width + i]; + } + } + + return surface; +} + +auto createPixels(Range)(Range r) +{ + struct PixelRange + { + private RGBColor[] pixels; + + this(Range)(Range r) + if (is(ElementType!Range == RGBColor)) + { + pixels = r.array; + } + + @property + { + RGBColor back() + { + return pixels.back; + } + + bool empty() + { + return pixels.empty; + } + + RGBColor front() + { + return pixels.front; + } + } + + void popFront() + { + pixels.popFront(); + } + + void popBack() + { + pixels.popBack(); + } + + PixelRange save() + { + return this; + } + } + + return PixelRange(r); +} + + +auto createFences(T, U)(Surface surface, T width, U height) +{ + alias Range = typeof(createPixels([RGBColor.init])); + + struct FenceRange(T, U, Range) + if (allArithmetic!(T, U)) + { + private Range[] pixelsRange; + + this(T, U)(Surface surface, T width, U height) + if (allArithmetic!(T, U)) + { + int halfFenceWidth = cast(int) width / 2; + int halfFenceHeight = cast(int) height / 2; + + for (int i = 0; i < surface.getHeight!int; i++) + { + for (int j = 0; j < surface.getWidth!int; j++) + { + ElementType!Range[] fenceAccumulator; + + for (int w = 0; w < cast(int) width; w++) + { + for (int h = 0; h < cast(int) height; h++) + { + auto indexW = j + (halfFenceWidth - w); + auto indexH = i + (halfFenceHeight - h); + if ((indexW < 0) || (indexH >= surface.getArea!int)) + { + fenceAccumulator ~= new RGBColor(0, 0, 0); + } + else + { + fenceAccumulator ~= surface[indexW, indexH]; + } + } + } + + pixelsRange ~= createPixels(fenceAccumulator); + } + } + } + + @property + { + Range back() + { + return pixelsRange.back; + } + + bool empty() + { + return (pixelsRange.length == 0); + } + + Range front() + { + return pixelsRange.front; + } + } + + void popFront() + { + pixelsRange.popFront; + } + + void popBack() + { + pixelsRange.popBack; + } + } + + return FenceRange!(T, U, Range)(surface, width, height); +} diff --git a/source/rip/concepts/surface.d b/source/rip/concepts/surface.d new file mode 100644 index 0000000..cb37c71 --- /dev/null +++ b/source/rip/concepts/surface.d @@ -0,0 +1,155 @@ +module rip.concepts.surface; + +private +{ + import std.algorithm; + import std.conv; + import std.range : array, iota, zip; + import std.stdio : File; + import std.string; + + import rip.concepts.color; + import rip.concepts.ranges; + import rip.concepts.templates; +} + +// Универсальный тип для любых изображений +class Surface { + private + { + RGBColor[] pixels; + size_t width; + size_t height; + + // Расчет одномерного индекса,с учетом возможного выхода за границы + auto calculateRealIndex(T)(T i) + { + auto N = cast(size_t) i; + auto S = width * height; + + return clamp(N, 0, S); + } + + // Перевод двумерных индексов в одномерный индекс, с учетом возможного выхода за границы + auto calculateRealIndex(T, U)(T i, U j) + if (allArithmetic!(T, U)) + { + auto W = cast(size_t) clamp(i, 0, width - 1); + auto H = cast(size_t) clamp(j, 0, height - 1); + auto S = width * height; + + return clamp(W + H * width, 0, S); + } + } + + // Параметризованный конструктор + this(T, U)(T width, U height, RGBColor rgbColor = new RGBColor(0, 0, 0)) + if (allArithmetic!(T, U)) + { + assert(width > 0); + assert(height > 0); + + this.width = width; + this.height = height; + + pixels = map!(a => rgbColor)(iota(width * height)).array; + } + + // Длина изображения в пикселах + mixin(addTypedGetter!("width", "getWidth")); + + // Ширина изображения в пикселах + mixin(addTypedGetter!("height", "getHeight")); + + // Общее количество пикселей в изображении + mixin(addTypedGetter!("(width * height)", "getArea")); + + @property + { + // Изменяемая копия изображения + Surface dup() + { + auto duplicateImage = new Surface(width, height); + + foreach (i; 0 .. width) + { + foreach (j; 0 .. height) + { + duplicateImage[i, j] = pixels[calculateRealIndex(i, j)]; + } + } + + return duplicateImage; + } + + // Неизменяемая копия изображения + immutable(Surface) idup() + { + return cast(immutable) this.dup; + } + } + + // Обращение к пикселу с помощью одного индекса + RGBColor opIndex(T)(T i) + if (allArithmetic!T) + { + return pixels[calculateRealIndex(i)]; + } + + // Обращение к пикселу с помощью двух индексов + RGBColor opIndex(T, U)(T i, U j) + if (allArithmetic!(T, U)) + { + return pixels[calculateRealIndex(i, j)]; + } + + // Присвоение пикселу значения + void opIndexAssign(T)(RGBColor rgbColor, T i) + if (allArithmetic!T) + { + pixels[calculateRealIndex(i)] = rgbColor; + } + + // То же самое, но в случае двумерных индексов + void opIndexAssign(T, U)(RGBColor rgbColor, T i, U j) + if (allArithmetic!(T, U)) + { + pixels[calculateRealIndex(i, j)] = rgbColor; + } + + auto getPixelsRange() { + return createPixels(pixels); + } + + auto getPixels() const { + return pixels; + } +}; + + +// Сложение двух картинок +mixin(addBinaryImageOperation!("+","add")); + +// Вычитание двух картинок +mixin(addBinaryImageOperation!("-","subtract")); + +// Умножение двух картинок +mixin(addBinaryImageOperation!("*","multiply")); + +// Деление двух картинок +mixin(addBinaryImageOperation!("/","divide")); + +// Остаток от деление одной картинки на другую +mixin(addBinaryImageOperation!("%","mod")); + +// Возведение в степень +mixin(addBinaryImageOperation!("^^","power")); + +// Логическое "И" двух картинок +mixin(addBinaryImageOperation!("&","and")); + +// Логическое "ИЛИ" двух картинок +mixin(addBinaryImageOperation!("|","or")); + +// Исключающее "ИЛИ" двух картинок +mixin(addBinaryImageOperation!("^","xor")); \ No newline at end of file diff --git a/source/rip/concepts/templates.d b/source/rip/concepts/templates.d new file mode 100644 index 0000000..0ae1d3e --- /dev/null +++ b/source/rip/concepts/templates.d @@ -0,0 +1,78 @@ +module rip.concepts.templates; + +private +{ + import std.meta : allSatisfy; + import std.range : zip; + import std.traits : isIntegral, isFloatingPoint, Unqual; + + import rip.concepts.color; + import rip.concepts.surface; +} + +template allArithmetic(T...) + if (T.length >= 1) +{ + template isNumberType(T) + { + enum bool isNumberType = isIntegral!(Unqual!T) || isFloatingPoint!(Unqual!T); + + } + + enum bool allArithmetic = allSatisfy!(isNumberType, T); +} + + +template addTypedGetter(string propertyVariableName, string propertyName) +{ + import std.string : format; + + const char[] addTypedGetter = format( + ` + @property + { + T %2$s(T)() const + { + alias typeof(return) returnType; + return cast(returnType) %1$s; + } + }`, + propertyVariableName, + propertyName + ); +} + + +// бинарные операции на целом изображении при условии, что аналогичные операции +// определены для RGBColor +template addBinaryImageOperation(string operationSign, string operationName) +{ + import std.string : format; + + const char[] addBinaryImageOperation = format( + `auto %2$s(Surface lhs, Surface rhs) + { + assert(lhs.getWidth!ulong == rhs.getWidth!ulong); + assert(lhs.getHeight!ulong == rhs.getHeight!ulong); + + RGBColor[] pixels; + + auto intermediateResult = zip( + lhs.getPixelsRange, + rhs.getPixelsRange + ); + + foreach (pixel; intermediateResult) + { + pixels ~= pixel[0] %1$s pixel[1]; + } + + return pixels.toSurface( + lhs.getWidth!ulong, + lhs.getHeight!ulong + ); + }`, + operationSign, + operationName + ); +} \ No newline at end of file diff --git a/source/rip/draw/ifs.d b/source/rip/draw/ifs.d new file mode 100644 index 0000000..57b0873 --- /dev/null +++ b/source/rip/draw/ifs.d @@ -0,0 +1,142 @@ +module rip.draw.ifs; + +private +{ + import std.random; + + import rip.concepts.color; + import rip.concepts.surface; + import rip.concepts.templates; + import rip.draw.primitives; +} + +// Одно уравнение итерируемой системы функций +class Equation +{ + private + { + float a; + float b; + float c; + float d; + float e; + float f; + } + + // Коэффициент a + mixin(addTypedGetter!("a", "getA")); + + // Коэффициент b + mixin(addTypedGetter!("b", "getB")); + + // Коэффициент c + mixin(addTypedGetter!("c", "getC")); + + // Коэффициент d + mixin(addTypedGetter!("d", "getD")); + + // Коэффициент e + mixin(addTypedGetter!("e", "getE")); + + // Коэффициент f + mixin(addTypedGetter!("e", "getF")); + + // Установить коэффициент a + void setA(T)(T a) + if (allArithmetic!T) + { + this.a = cast(float) x; + } + + // Установить коэффициент b + void setB(T)(T b) + if (allArithmetic!T) + { + this.b = cast(float) b; + } + + // Установить коэффициент c + void setC(T)(T c) + if (allArithmetic!T) + { + this.c = cast(float) c; + } + + // Установить коэффициент d + void setD(T)(T d) + if (allArithmetic!T) + { + this.d = cast(float) d; + } + + // Установить коэффициент e + void setE(T)(T e) + if (allArithmetic!T) + { + this.e = cast(float) e; + } + + // Установить коэффициент f + void setF(T)(T f) + if (allArithmetic!T) + { + this.f = cast(float) f; + } +} + +// Представление системы уравнений в виде ассоциативного массива +// (в роли ключа - вероятность применения уравнения, в ролди значения - одно уравнение системы) +template EquationSystem(T) + if (allArithmetic!T) +{ + alias EquationSystem = Equation[T]; +} + +// Класс для итерируемой системы функций +class IFS(T) +{ + private + { + Surface surface; + RGBColor color; + EquationSystem!T equationSystem; + ulong numberOfGeneration; + + float x; + float y; + } + + // Универсальный конструктор класса + this(U, V, W)(Surface surface, RGBColor color, EquationSystem!T equationSystem, + U x, V y, W numberOfGeneration) + if (allArithmetic!(T, U, V, W)) + { + this.surface = surface; + this.color = color; + this.equationSystem = equationSystem; + this.numberOfGeneration = cast(ulong) numberOfGeneration; + + this.x = cast(float) x; + this.y = cast(float) y; + } + + // Выполнить команды рисования итерируемой системы функций + EquationSystem!T execute() + { + for (ulong i = 0; i < numberOfGeneration; i++) + { + drawPoint(surface, color, x, y); + + auto d = dice(equationSystem.keys); + Equation eq = equationSystem[d]; + + auto mul0 = eq.getA!float * x + eq.getB!float * y; + auto mul1 = eq.getC!float * x + eq.getD!float * y; + + x = mul0 + eq.getE!float; + y = mul1 + eq.getF!float; + } + + return equationSystem; + } +} \ No newline at end of file diff --git a/source/rip/draw/lsystem.d b/source/rip/draw/lsystem.d new file mode 100644 index 0000000..5cc7871 --- /dev/null +++ b/source/rip/draw/lsystem.d @@ -0,0 +1,155 @@ +module rip.draw.lsystem; + +private +{ + import std.math; + import std.string; + + import rip.concepts; + import rip.draw.turtle; +} + +// Правила переписывания +alias RewritingRules = string[string]; + +// параметры L-системы +class LSystemParameters +{ + private + { + float x; + float y; + float stepIncrement; + float angleIncrement; + ulong numberOfGeneration; + } + + this(S, T, U, V, W)(S x, T y, U stepIncrement, V angleIncrement, W numberOfGeneration) + if (allArithmetic!(S, T, U, V, W)) + { + this.x = cast(float) x; + this.y = cast(float) y; + this.stepIncrement = cast(float) stepIncrement; + this.angleIncrement = cast(float) angleIncrement; + this.numberOfGeneration = cast(uint) abs(numberOfGeneration); + } + + + mixin(addTypedGetter!("x", "getX")); + mixin(addTypedGetter!("y", "getY")); + mixin(addTypedGetter!("stepIncrement", "getStep")); + mixin(addTypedGetter!("angleIncrement", "getAngle")); + mixin(addTypedGetter!("numberOfGeneration", "getGeneration")); + + + + void setX(T)(T x) + if (allArithmetic!T) + { + this.x = cast(float) x; + } + + void setY(T)(T y) + if (allArithmetic!T) + { + this.y = cast(float) y; + } + + void setStep(T)(T angle) + if (allArithmetic!T) + { + this.stepIncrement = cast(float) stepIncrement; + } + + void setAngle(T)(T angle) + if (allArithmetic!T) + { + this.angleIncrement = cast(float) angleIncrement; + } + + void setGeneration(T)(T angle) + if (allArithmetic!T) + { + this.numberOfGeneration = cast(uint) numberOfGeneration; + } +} + +class LSystem +{ + private + { + Surface surface; + RGBColor color; + + LSystemParameters parameters; + RewritingRules rules; + string axiom; + + // процедура переписывания строки + string rewrite(string sourceTerm, string termForRewrite, string newTerm) + { + auto acc = ""; + auto search = 0; + + for (uint i = 0; i < sourceTerm.length; i++) + { + auto index = indexOf(sourceTerm[search .. search + termForRewrite.length], termForRewrite); + + if (index != -1) + { + search += termForRewrite.length; + acc ~= newTerm; + } + else + { + search++; + acc ~= sourceTerm[search-1]; + } + } + + return acc; + } + } + + this(Surface surface, RGBColor color, LSystemParameters parameters, + string axiom, RewritingRules rules) + { + this.surface = surface; + this.color = color; + this.parameters = parameters; + this.axiom = axiom; + this.rules = rules; + } + + LSystemParameters execute() + { + // новое состояние черепахи + auto turtleState = new TurtleState( + parameters.getX!float, + parameters.getY!float, + parameters.getAngle!float + ); + // новая черепаха + auto turtle = new Turtle(surface, color, turtleState, + parameters.getStep!float, + parameters.getAngle!float + ); + + // команды L-системы + auto lSystemCmd = axiom; + + // запуск процедуры переписывания + for (ulong i = 1; i < parameters.getGeneration!ulong; i++) + { + foreach (rule; rules.keys) + { + lSystemCmd = rewrite(lSystemCmd.idup, rule, rules[rule]); + } + } + + turtle.execute(lSystemCmd); + + return parameters; + } +} + diff --git a/source/rip/draw/mathgraphics.d b/source/rip/draw/mathgraphics.d new file mode 100644 index 0000000..39594a3 --- /dev/null +++ b/source/rip/draw/mathgraphics.d @@ -0,0 +1,84 @@ +module rip.draw.mathgraphics; + +private +{ + import std.algorithm; + import std.math; + import std.range; + import std.traits; + + import rip.concepts; + + import rip.draw.primitives : drawPoint; + + // отрисовка по сразу двум параметрам-диапазонам + // не предназначена для внешнего использования + auto drawTwoRanges(First, Second)(Surface surface, RGBColor rgbColor, + First first, Second second) + if (allArithmetic!(ElementType!First, ElementType!Second)) + { + assert(!first.empty); + assert(!second.empty); + + foreach (xy; zip(first, second)) + { + surface.drawPoint(rgbColor, xy[0], xy[1]); + } + } +} + +alias drawDiscrete = drawTwoRanges; // рисование последовательностей + +// график некоторой функции на непрерывном диапазоне +auto drawFunctional(T, U, Range)(Surface surface, RGBColor rgbColor, + T delegate(U) func, Range r) + if (isInputRange!(Unqual!Range) && allArithmetic!(T, U, ElementType!Range)) +{ + assert(!r.empty); + + auto ys = map!(a => func(a))(r); + + drawTwoRanges(surface, rgbColor, r, ys); +} + + +// график параметрической функции +auto drawParametrical(T, U, V, W, Range)(Surface surface, RGBColor rgbColor, + T delegate(U) funcX, V delegate(W) funcY, Range r) + if (isInputRange!(Unqual!Range) && allArithmetic!(T, U, V, W, ElementType!Range)) +{ + + auto xs = map!(a => funcX(a))(r); + auto ys = map!(a => funcY(a))(r); + + drawTwoRanges(surface, rgbColor, xs, ys); +} + + +// рисование функции в полярных координатах (углы в градусах) +auto drawPolarInDegrees(T, U, Range)(Surface surface, RGBColor rgbColor, + T delegate(U) func, Range r) + if (isInputRange!(Unqual!Range) && allArithmetic!(T, U, ElementType!Range)) +{ + assert(!r.empty); + + auto phi = map!(a => a * (PI / 180.0))(r).array; + auto xs = map!(a => func(a) * cos(a))(phi); + auto ys = map!(a => func(a) * sin(a))(a); + + drawTwoRanges(surface, rgbColor, r, ys); +} + + +// рисование функции в полярных координатах (углы в радианах) +auto drawPolarInRadians(T, U, Range)(Surface surface, RGBColor rgbColor, + T delegate(U) func, Range r) + if (isInputRange!(Unqual!Range) && allArithmetic!(T, U, ElementType!Range)) +{ + assert(!r.empty); + + auto xs = map!(a => func(a) * cos(a))(r); + auto ys = map!(a => func(a) * sin(a))(r); + + drawTwoRanges(surface, rgbColor, r, ys); +} diff --git a/source/rip/draw/package.d b/source/rip/draw/package.d new file mode 100644 index 0000000..c7bb05f --- /dev/null +++ b/source/rip/draw/package.d @@ -0,0 +1,9 @@ +module rip.draw; + +public { + import rip.draw.ifs; + import rip.draw.lsystem; + import rip.draw.mathgraphics; + import rip.draw.primitives; + import rip.draw.turtle; +} \ No newline at end of file diff --git a/source/rip/draw/primitives.d b/source/rip/draw/primitives.d new file mode 100644 index 0000000..ba67b28 --- /dev/null +++ b/source/rip/draw/primitives.d @@ -0,0 +1,204 @@ +module rip.draw.primitives; + +private +{ + import std.algorithm; + import std.math; + + import rip.concepts.color; + import rip.concepts.surface; + import rip.concepts.templates; +} + +// Рисование точки +auto drawPoint(T, S)(Surface surface, RGBColor rgbColor, T x, S y) + if (allArithmetic!(T, S)) +{ + surface[x, y] = rgbColor; +} + +// Рисование линии с помощью цифрового дифференциального анализатора +auto drawDDALine(T, U, V, W)(Surface surface, RGBColor rgbColor,T x1, U y1, V x2, W y2) + if (allArithmetic!(T, U, V, W)) +{ + auto deltaX = abs(x1 - x2); + auto deltaY = abs(y1 - y2); + auto L = max(deltaX, deltaY); + + if (L == 0) + { + surface[x1, y1] = rgbColor; + } + + auto dx = (x2 - x1) / L; + auto dy = (y2 - y1) / L; + float x = x1; + float y = y1; + + L++; + while(L--) + { + x += dx; + y += dy; + surface[x1, y1] = rgbColor; + } +} + +// Рисование линии методом Брезенхема +void drawBresenhamLine(T, U, V, W)(Surface surface, RGBColor color, T x1, U y1, V x2, W y2) + if (allArithmetic!(T, U, V, W)) +{ + float a = cast(float) x1; + float b = cast(float) y1; + float c = cast(float) x2; + float d = cast(float) y2; + + float dx = (x2 - x1 >= 0 ? 1 : -1); + float dy = (y2 - y1 >= 0 ? 1 : -1); + + float lengthX = abs(x2 - x1); + float lengthY= abs(y2 - y1); + float length = max(lengthX, lengthY); + + if (length == 0) + { + surface[x1, y1] = color; + } + + if (lengthY <= lengthX) + { + float x = x1; + float y = y1; + + length++; + while (length--) + { + surface[x, y] = color; + x += dx; + y += (dy * lengthY) / lengthX; + } + } + else + { + float x = x1; + float y = y1; + + length++; + while(length--) + { + surface[x, y] = color; + x += (dx * lengthX) / lengthY; + y += dy; + } + } +} + +// Рисование окружности +void drawCircle(T, U, V)(Surface surface, RGBColor color, T x, U y, V r) + if (allArithmetic!(T, U, V)) +{ + assert (r >= 0); + + auto a = cast(float) x; + auto b = cast(float) y; + auto c = cast(float) r; + + for (float i = 0.0; i < 360.0; i += 0.01) + { + auto X = cast(int) (a + c * cos(i * PI / 180.0)); + auto Y = cast(int) (b + c * sin(i * PI / 180.0)); + surface[X, Y] = color; + } +} + +// Рисование конических сечений +void drawConicSection(T, U, V, W)(Surface surface, RGBColor color, T x, U y, V l, W e) + if (allArithmetic!(T, U, V, W)) +{ + auto a = cast(float) x; + auto b = cast(float) y; + auto c = cast(float) l; + auto d = cast(float) e; + + for (float i = 0.0; i < 360.0; i += 0.01) + { + auto r = c / (1.0 - d * cos(i * PI / 180.0)); + auto X = cast(int) (a + c * cos(i * PI / 180.0)); + auto Y = cast(int) (b + c * sin(i * PI / 180.0)); + surface[X, Y] = color; + } +} + + +// Рисование прямоугольника +void drawRectangle(T, U, V, W)(Surface surface, RGBColor color, T x, U y, V w, W h) + if (allArithmetic!(T, U, V, W)) +{ + assert(w >= 0); + assert(h >= 0); + + auto X = cast(int) x; + auto Y = cast(int) y; + auto WW = cast(int) w; + auto HH = cast(int) h; + + for (int a = 0; a < HH; a++) + { + surface[X, Y + a] = color; + } + + for (uint b = 0; b < WW; b++) + { + sutface[X + b, Y + HH] = color; + } + + for (uint c = 0; c < HH; c++) + { + surface[X + WW, Y + c] = color; + } + + for (uint d = 0; d < WW; d++) + { + surface[X + d, Y] = color; + } +} + +// Окружность с заливкой +void drawFilledCircle(T, U, V)(Surface surface, RGBColor color, T x, U y, V r) + if (allArithmetic!(T, U, V)) +{ + auto a = cast(float) x; + auto b = cast(float) y; + auto c = cast(float) r; + + for (float i = 0.0; i < 360.0; i += 0.01) + { + for (float j = 0; j < c; j++) + { + auto X = cast(int) (a + j * cos(i * PI / 180.0)); + auto Y = cast(int) (b + j * sin(i * PI / 180.0)); + surface[X, Y] = color; + } + } +} + +// Прямоугольник с заливкой +void drawFilledRectangle(T, U, V, W)(Surface surface, RGBColor color, T x, U y, W w, H h) + if (allArithmetic!(T, U, V, W)) +{ + assert(w >= 0); + assert(h >= 0); + + auto X = cast(int) x; + auto Y = cast(int) y; + auto WW = cast(int) w; + auto HH = cast(int) h; + + for (int i = 0; i < WW; i++) + { + for (int j = 0; j < HH; j++) + { + surface[X + i, Y + j] = color; + } + } +} \ No newline at end of file diff --git a/source/rip/draw/turtle.d b/source/rip/draw/turtle.d new file mode 100644 index 0000000..b7cf76b --- /dev/null +++ b/source/rip/draw/turtle.d @@ -0,0 +1,206 @@ +module rip.draw.turtle; + +private +{ + import std.math; + import std.random; + + import rip.concepts; + import rip.draw.primitives : drawDDALine; +} + +// состояние черепахи +class TurtleState +{ + private + { + float x; + float y; + float angle; + } + + this(T, U, V)(T x, U y, V angle) + if (allArithmetic!(T, U, V)) + { + this.x = cast(float) x; + this.y = cast(float) y; + this.angle = cast(float) angle; + } + + mixin(addTypedGetter!("x", "getX")); + + mixin(addTypedGetter!("y", "getY")); + + mixin(addTypedGetter!("angle", "getAngle")); + + void setX(T)(T x) + if (allArithmetic!T) + { + this.x = cast(float) x; + } + + void setY(T)(T y) + if (allArithmetic!T) + { + this.y = cast(float) y; + } + + void setAngle(T)(T angle) + if (allArithmetic!T) + { + this.angle = cast(float) angle; + } +} + + +// И теперь в наших руках появляется мощное оружие - исполнитель "черепаха"... +class Turtle +{ + private + { + Surface surface; + RGBColor color; + + TurtleState[] stateStack; + TurtleState state; + + float stepIncrement; + float angleIncrement; + } + + this(T, U)(Surface surface, RGBColor color, TurtleState state, T stepIncrement, U angleIncrement) + if (allArithmetic!(T, U)) + { + this.surface = surface; + this.color = color; + this.state = state; + this.stepIncrement = cast(float) stepIncrement; + this.angleIncrement = cast(float) angleIncrement; + } + + // шаг вперед с отрисовкой следа + TurtleState drawStep() + { + float newX, newY; + + newX = state.getX!float + cos(state.getAngle!float) * stepIncrement; + newY = state.getY!float - sin(state.getAngle!float) * stepIncrement; + + surface.drawDDALine(color, + state.getX!float, + state.getY!float, + newX, + newY + ); + + state.setX(newX); + state.setY(newY); + + return state; + } + + // шаг вперед без отрисовки следа + TurtleState moveStep() + { + float newX, newY; + + newX = state.getX!float + cos(state.getAngle!float) * stepIncrement; + newY = state.getY!float - sin(state.getAngle!float) * stepIncrement; + + state.setX(newX); + state.setY(newY); + + return state; + } + + // поворот влево + TurtleState rotateLeft() + { + float newAngle; + + newAngle = state.getAngle!float + angleIncrement; + + state.setAngle(newAngle); + + return state; + } + + // поворот вправо + TurtleState rotateRight() + { + float newAngle; + + newAngle = state.getAngle!float - angleIncrement; + + state.setAngle(newAngle); + + return state; + } + + // поворот на случайный угол + TurtleState rotateRandom() + { + float newAngle; + + auto rndGenerator = new Random(unpredictableSeed); + newAngle = uniform(-2 * PI, 2 * PI, rndGenerator); + + state.setAngle(newAngle); + + return state; + } + + // сохранить состояние черепахи + TurtleState saveState() + { + stateStack ~= state; + + return state; + } + + // восстановить состояние черепахи + TurtleState restoreState() + { + state = stateStack[$-1]; + stateStack = stateStack[0 .. $-1]; + + return state; + } + + // выполнить команду с помощью черепахи + TurtleState execute(string s) + { + TurtleState currentState; + for (int i = 0; i < s.length; i++) + { + switch(s[i]) + { + case 'F': + currentState = drawStep(); + break; + case 'f': + currentState = moveStep(); + break; + case '+': + currentState = rotateRight(); + break; + case '-': + currentState = rotateLeft(); + break; + case '?': + currentState = rotateRandom(); + break; + case '[': + currentState = saveState(); + break; + case ']': + currentState = restoreState(); + break; + default: + break; + } + } + + return currentState; + } +} diff --git a/source/rip/dsp/package.d b/source/rip/dsp/package.d new file mode 100644 index 0000000..543be6e --- /dev/null +++ b/source/rip/dsp/package.d @@ -0,0 +1,7 @@ +module rip.dsp; + +public { + import rip.dsp.transforms.hadamard; + import rip.dsp.transforms.haar; + import rip.dsp.transforms.slant; +} diff --git a/source/rip/dsp/transforms/haar.d b/source/rip/dsp/transforms/haar.d new file mode 100644 index 0000000..03d9f15 --- /dev/null +++ b/source/rip/dsp/transforms/haar.d @@ -0,0 +1,72 @@ +module rip.dsp.transforms.haar; + +private +{ + import std.math; + + import rip.processing.filters.linear; + + static float SQ2 = cast(float) SQRT2; +} + + +class Haar2 : LinearFilter +{ + this() + { + apertureWidth = 2; + apertureHeight = 2; + apertureDivider = 1.0 / SQ2; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f ], + [ 1.0f, -1.0f ], + ] + ); + } +} + + +class Haar4 : LinearFilter +{ + this() + { + apertureWidth = 4; + apertureHeight = 4; + apertureDivider = 0.5f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f, 1.0f ], + [ 1.0f, 1.0f, -1.0f -1.0f ], + [ SQ2, -SQ2, 0.0f, 0.0f ], + [ 0.0f, 0.0f, SQ2, -SQ2 ], + ] + ); + } +} + + +class Haar8 : LinearFilter +{ + this() + { + apertureWidth = 8; + apertureHeight = 8; + apertureDivider = 1.0f / sqrt(8.0f); + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f ], + [ 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f ], + [ SQ2, SQ2, -SQ2, -SQ2, 0.0f, 0.0f, 0.0f, 0.0f ], + [ 0.0f, 0.0f, 0.0f, 0.0f, SQ2, SQ2, -SQ2, -SQ2 ], + [ 2.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ], + [ 0.0f, 0.0f, 2.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f ], + [ 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, -2.0f, 0.0f, 0.0f ], + [ 0.0f, 0.0f , 0.0f, 0.0f, 0.0f, 0.0f, 2.0f, -2.0f ], + ] + ); + } +} \ No newline at end of file diff --git a/source/rip/dsp/transforms/hadamard.d b/source/rip/dsp/transforms/hadamard.d new file mode 100644 index 0000000..8092599 --- /dev/null +++ b/source/rip/dsp/transforms/hadamard.d @@ -0,0 +1,72 @@ +module rip.dsp.transforms.hadamard; + +private +{ + import std.math; + + import rip.processing.filters.linear; + + static float SQ2 = cast(float) SQRT2; +} + + +class Hadamard2 : LinearFilter +{ + this() + { + apertureWidth = 2; + apertureHeight = 2; + apertureDivider = 1.0 / SQ2; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f ], + [ 1.0f, -1.0f ], + ] + ); + } +} + + +class Hadamard4: LinearFilter +{ + this() + { + apertureWidth = 4; + apertureHeight = 4; + apertureDivider = 0.5f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f, 1.0f ], + [ 1.0f, -1.0f, 1.0f -1.0f ], + [ 1.0f, 1.0f, -1.0f, -1.0f ], + [ 1.0f, -1.0f, -1.0f, 1.0f ], + ] + ); + } +} + + +class Hadamard8 : LinearFilter +{ + this() + { + apertureWidth = 8; + apertureHeight = 8; + apertureDivider = 1.0f / SQ2; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f ], + [ 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f ], + [ 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f ], + [ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f ], + [ 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f ], + [ 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f ], + [ 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f ], + [ 1.0f, -1.0f , -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f ], + ] + ); + } +} \ No newline at end of file diff --git a/source/rip/dsp/transforms/slant.d b/source/rip/dsp/transforms/slant.d new file mode 100644 index 0000000..d700b4c --- /dev/null +++ b/source/rip/dsp/transforms/slant.d @@ -0,0 +1,50 @@ +module rip.dsp.transforms.slant; + +private +{ + import std.math; + + import rip.processing.filters.linear; + + static float SQ2 = cast(float) SQRT2; + static float SQ5 = cast(float) (1.0f / sqrt(5.0)); + static float TR = cast(float) (3.0f / sqrt(5.0)); +} + + +class Slant2 : LinearFilter +{ + this() + { + apertureWidth = 2; + apertureHeight = 2; + apertureDivider = 1.0 / SQ2; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f ], + [ 1.0f, -1.0f ], + ] + ); + } +} + + +class Slant4: LinearFilter +{ + this() + { + apertureWidth = 4; + apertureHeight = 4; + apertureDivider = 0.5f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f, 1.0f ], + [ TR, SQ5, -SQ5, -TR ], + [ 1.0f, -1.0f, -1.0f, 1.0f ], + [ SQ5, -TR, SQ5, TR ], + ] + ); + } +} diff --git a/source/rip/io/dlib.d b/source/rip/io/dlib.d new file mode 100644 index 0000000..fee688f --- /dev/null +++ b/source/rip/io/dlib.d @@ -0,0 +1,48 @@ +module rip.io.dlib; + +private { + import rip.io; +} + +public: + +//Если привязка используется, то и доступ к библеотеке должен присутствовать +import dlib.image; + +//alias superImageToSurface = convert!(Surface); +Surface superImageToSurface(SuperImage image) { + auto surface = new Surface(image.width, image.height); + + foreach(i; 0..image.width) { + foreach(j; 0..image.height) { + surface[i, j] = color4ToRgbColor(image[i, j]); + } + } + + return surface; +} + +SuperImage surfaceToSuperImage(in Surface _surface) { + //opIndex должна быть const, при исправлении строчку ниже удалить + auto surface = cast(Surface)_surface; + + auto _image = image(surface.getWidth!uint, surface.getHeight!uint, 4, 8); + + foreach(i; 0.._image.width) { + foreach(j; 0.._image.height) { + _image[i, j] = rgbColorToColor4(surface[i, j]); + } + } + + return _image; +} + +RGBColor color4ToRgbColor(in Color4f color) { + return new RGBColor(color.r * 255, color.g * 255, color.b * 255); +} + +Color4f rgbColorToColor4(in RGBColor color) { + return Color4f( color.red!float / 255, + color.green!float / 255, + color.blue!float / 255, 1); +} diff --git a/source/rip/io/file.d b/source/rip/io/file.d new file mode 100644 index 0000000..4d4ecdf --- /dev/null +++ b/source/rip/io/file.d @@ -0,0 +1,53 @@ +module rip.io.file; + +protected { + import std.stdio; + + import rip.io; + import rip.concepts; +} + +/++ ++ Saves data to file with formats ++ Params: ++ surface = input data ++ worker = format worker ++ name = file name ++ Returns: ++ input data for using in UFCS ++/ +S toFile(S)(S surface, const FormatWorker!(S) worker, string name) { + return worker.save(surface, name); +} + +auto fromFormatFile(S)(const FormatWorker!(S) worker, string name) { + return worker.load(name); +} + +auto fromFile(in string name) { + File file; + file.open(name, "r"); + + return detectFormat(file).decode(file); +} + +alias FW = FormatWorker!(Surface); + +FW detectFormat(File file) { + if (P6.checkOnHeader(file)) + return cast(FW)P6; + + else if(BMP.checkOnHeader(file)) + return cast(FW)BMP; + + else if(JPEG.checkOnHeader(file)) + return cast(FW)JPEG; + + else if(TGA.checkOnHeader(file)) + return cast(FW)TGA; + + else if(PNG.checkOnHeader(file)) + return cast(FW)PNG; + + assert(0, "File format is not recognized"); +} diff --git a/source/rip/io/formats/bmp.d b/source/rip/io/formats/bmp.d new file mode 100644 index 0000000..2a0d3c9 --- /dev/null +++ b/source/rip/io/formats/bmp.d @@ -0,0 +1,45 @@ +module rip.io.formats.bmp; + +private { + import std.stdio : File; + import std.string; + + import rip.concepts; + import rip.io.interfaces; + + import rip.io.dlib; +} + +public: +static const auto BMP = new BMPWorker; + +private: + +static final class BMPWorker : FormatWorker!(Surface) { + + override Surface save(in Surface surface, in string filename) const { + import rip.io.dlib; + + saveImage(surfaceToSuperImage(surface), filename); + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + + override Surface decode(File file) const { + return this.load(file.name); + } + + override bool checkOnHeader(File file) const { + ubyte[2] buff; + file.rawRead(buff); + file.seek(0); + return buff == [0x42, 0x4D]; + } + + override Surface load(in string filename) const { + import rip.io.dlib; + + return superImageToSurface(loadBMP(filename)); + } +} diff --git a/source/rip/io/formats/jpeg.d b/source/rip/io/formats/jpeg.d new file mode 100644 index 0000000..a7a6bed --- /dev/null +++ b/source/rip/io/formats/jpeg.d @@ -0,0 +1,45 @@ +module rip.io.formats.jpeg; + +private { + import std.stdio : File; + import std.string; + + import rip.concepts; + import rip.io.interfaces; + + import rip.io.dlib; +} + +public: +static const auto JPEG = new JPEGWorker; + +private: + +static final class JPEGWorker : FormatWorker!(Surface) { + + override Surface save(in Surface surface, in string filename) const { + import rip.io.dlib; + + saveImage(surfaceToSuperImage(surface), filename); + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + + override Surface decode(File file) const { + return this.load(file.name); + } + + override bool checkOnHeader(File file) const { + ubyte[2] buff; + file.rawRead(buff); + file.seek(0); + return buff == [0xFF, 0xD8]; + } + + override Surface load(in string filename) const { + import rip.io.dlib; + + return superImageToSurface(loadJPEG(filename)); + } +} diff --git a/source/rip/io/formats/package.d b/source/rip/io/formats/package.d new file mode 100644 index 0000000..cb4eedd --- /dev/null +++ b/source/rip/io/formats/package.d @@ -0,0 +1,10 @@ +module rip.io.formats; + +public { + import rip.io.formats.pam; + import rip.io.formats.ppm; + import rip.io.formats.bmp; + import rip.io.formats.jpeg; + import rip.io.formats.tga; + import rip.io.formats.png; +} diff --git a/source/rip/io/formats/pam.d b/source/rip/io/formats/pam.d new file mode 100644 index 0000000..0163e57 --- /dev/null +++ b/source/rip/io/formats/pam.d @@ -0,0 +1,123 @@ +module rip.io.formats.pam; + +private { + import std.algorithm; + import std.conv : parse; + + import std.stdio : File; + import std.string; + import std.algorithm; + import std.stdio; + + import rip.io.interfaces; + import rip.concepts; +} + +public: +static const auto PAM = new PamWorker; + +private: + +static final class PamWorker : FormatWorker!(Surface) { + override Surface save(in Surface surface, in string filename) const { + File file; + + with (file) + { + open(filename, "w"); + writeln("P7"); + writeln("WIDTH ", surface.getWidth!uint); + writeln("HEIGHT ", surface.getHeight!uint); + writeln("DEPTH 3"); + writeln("MAXVAL 255"); + writeln("TUPLTYPE RGB"); + writeln("ENDHDR"); + + //раскомментировать, когда ситуация с этим шаблоном разрешится + //mixin colorsToFile; + + void toFile(in RGBColor color) { + file.write( + color.red!char, + color.green!char, + color.blue!char); + } + + surface + .getPixels() + .each!toFile; + + close(); + } + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + override Surface decode(File file) const { + with (file) { + if (readln().strip == "P7") + { + size_t width, height; + auto strData = readln.split; + + while(strData[0] != "ENDHDR") { + switch(strData[0]) { + case "WIDTH": width = parse!size_t(strData[1]); break; + case "HEIGHT": height = parse!size_t(strData[1]); break; + case "DEPTH": { + if(parse!ubyte(strData[1]) != 3) + assert(0, "P7: Only files with DEPTH 3 are supported"); + } break; + case "MAXVAL": { + if(parse!uint(strData[1]) != 255) + assert(0, "P7: Only files with MAXVAL 255 are supported"); + } break; + case "TUPLTYPE": { + if(strData[1] != "RGB") + assert(0, "P7: Only files with TUPLTYPE RGB are supported"); + } break; + + default: break; + } + + strData = readln.split; + } + + auto buffer = new ubyte[width * 3]; + + Surface surface = new Surface(width, height); + + for (size_t i = 0; i < height; i++) + { + file.rawRead!ubyte(buffer); + for (size_t j = 0; j < width; j++) + { + + surface[j + i * width] = new RGBColor( + buffer[j * 3], + buffer[j * 3 + 1], + buffer[j * 3 + 2] + ); + } + } + + close(); + + return surface; + } + } + + assert(0, "PAM: Error. Decoding terminated."); + } + + override bool checkOnHeader(File file) const { + ubyte[2] buff; + file.rawRead(buff); + file.seek(0); + return buff == [0x50, 0x37]; + } + + override Surface load(in string name) const { + return null; + } +} diff --git a/source/rip/io/formats/png.d b/source/rip/io/formats/png.d new file mode 100644 index 0000000..94ed981 --- /dev/null +++ b/source/rip/io/formats/png.d @@ -0,0 +1,45 @@ +module rip.io.formats.png; + +private { + import std.stdio : File; + import std.string; + + import rip.concepts; + import rip.io.interfaces; + + import rip.io.dlib; +} + +public: +static const auto PNG = new PNGWorker; + +private: + +static final class PNGWorker : FormatWorker!(Surface) { + + override Surface save(in Surface surface, in string filename) const { + import rip.io.dlib; + + savePNG(surfaceToSuperImage(surface), filename); + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + + override Surface decode(File file) const { + return this.load(file.name); + } + + override bool checkOnHeader(File file) const { + ubyte[8] buff; + file.rawRead(buff); + file.seek(0); + return buff == [137, 80, 78, 71, 13, 10, 26, 10]; + } + + override Surface load(in string filename) const { + import rip.io.dlib; + + return superImageToSurface(loadPNG(filename)); + } +} diff --git a/source/rip/io/formats/ppm.d b/source/rip/io/formats/ppm.d new file mode 100644 index 0000000..6638e54 --- /dev/null +++ b/source/rip/io/formats/ppm.d @@ -0,0 +1,99 @@ +module rip.io.formats.ppm; + +private { + import std.algorithm; + import std.conv : parse; + + import std.stdio : File; + import std.string; + + import rip.concepts; + import rip.io.interfaces; +} + +public: +static const auto P6 = new P6Worker; + +private: + +static final class P6Worker : FormatWorker!(Surface) { + + override Surface save(in Surface surface, in string filename) const { + File file; + + with (file) + { + open(filename, "w"); + writeln("P6"); + writeln(surface.getWidth!uint, " ", surface.getHeight!uint); + writeln(255); + + //раскомментировать, когда ситуация с этим шаблоном разрешится + //mixin colorsToFile; + + alias toFile = (in a) => file.write( + a.red!char, + a.green!char, + a.blue!char); + + surface + .getPixels() + .each!toFile; + + close(); + } + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + + override Surface decode(File file) const { + with (file) { + if (readln().strip == "P6") + { + auto imageSize = readln.split; + auto width = parse!size_t(imageSize[0]); + auto height = parse!size_t(imageSize[1]); + readln(); + auto buffer = new ubyte[width * 3]; + + Surface surface = new Surface(width, height); + + for (size_t i = 0; i < height; i++) + { + file.rawRead!ubyte(buffer); + for (size_t j = 0; j < width; j++) + { + + surface[j + i * width] = new RGBColor( + buffer[j * 3], + buffer[j * 3 + 1], + buffer[j * 3 + 2] + ); + } + } + + close(); + + return surface; + } + } + + assert(0, "PPM: Error. Decoding terminated."); + } + + override bool checkOnHeader(File file) const { + ubyte[2] buff; + file.rawRead(buff); + file.seek(0); + return buff == [0x50, 0x36]; + } + + override Surface load(in string filename) const { + File file; + + file.open(filename, "r"); + + return this.decode(file); + } +} diff --git a/source/rip/io/formats/tga.d b/source/rip/io/formats/tga.d new file mode 100644 index 0000000..0bb0368 --- /dev/null +++ b/source/rip/io/formats/tga.d @@ -0,0 +1,46 @@ +module rip.io.formats.tga; + +private { + import std.stdio; + import std.string; + + import rip.concepts; + import rip.io.interfaces; + + import rip.io.dlib; +} + +public: +static const auto TGA = new TGAWorker; + +private: + +static final class TGAWorker : FormatWorker!(Surface) { + + override Surface save(in Surface surface, in string filename) const { + import rip.io.dlib; + + saveImage(surfaceToSuperImage(surface), filename); + + return cast(Surface)surface; + } + //вернёт false, если всё прошло успешно + + override Surface decode(File file) const { + return this.load(file.name); + } + + override bool checkOnHeader(File file) const { + ubyte[17] buff; + file.seek(-18, SEEK_END); + file.rawRead(buff); + file.seek(0); + return buff == "TRUEVISION-XFILE."; + } + + override Surface load(in string filename) const { + import rip.io.dlib; + + return superImageToSurface(loadTGA(filename)); + } +} diff --git a/source/rip/io/interfaces.d b/source/rip/io/interfaces.d new file mode 100644 index 0000000..0fc0224 --- /dev/null +++ b/source/rip/io/interfaces.d @@ -0,0 +1,77 @@ +module rip.io.interfaces; + +import std.stdio; + +/++ ++ Base interface for format workers ++ Can be used not only for Surface ++ Example: ++ ------------ ++ FormatWorker!int loader = new ... ++ int answer = 42; ++ int question; + ++ loader.save(42, "output.txt"); ++ question = loader.load("universe.txt") ++ ------------ ++/ +interface FormatWorker(T) { + /++ + + Saves data to file + + Params: + + surface = input data + + name = file name + + Returns: + + input data for using in UFCS + + + +/ + T save(in T surface, in string name) const; + //ошибка должна выбивать исключение + + /++ + + Loads data from file + + Params: + + filename = file name + + Returns: + + loaded data + +/ + T load(in string name) const; + + /++ + + Decodes data from file + + Params: + + file = file + + Returns: + + decoded data from file + +/ + T decode(File file) const; + + /++ + + Check file for header + + Params: + + file = file + + Returns: + + 'true' if file have header of this format + +/ + bool checkOnHeader(File file) const; +} + +//этому шаблону здесь не место +mixin template colorsToFile() { + import std.stdio; + void toFile(RGBColor color) { + file.write( + color.red!char, + color.green!char, + color.blue!char); + } + + /*surface.getPixelsRange().each!toFile;*/ + auto range = surface.getPixels(); + + import std.algorithm; + /*each!toFile(range);*/ + //Бага? + //source/io/interfaces.d(25,16): Error: function declaration without return type. (Note that constructors are always named 'this') + //source/io/interfaces.d(25,23): Error: no identifier for declarator each!toFile(range) +} diff --git a/source/rip/io/package.d b/source/rip/io/package.d new file mode 100644 index 0000000..0f589ab --- /dev/null +++ b/source/rip/io/package.d @@ -0,0 +1,9 @@ +module rip.io; + +public { + import rip.io.interfaces; + import rip.io.formats; + import rip.io.file; + + import rip.io.dlib; +} diff --git a/source/rip/package.d b/source/rip/package.d new file mode 100644 index 0000000..1e92110 --- /dev/null +++ b/source/rip/package.d @@ -0,0 +1,11 @@ +module rip; + +public { + import rip.concepts; + import rip.analysis; + import rip.draw; + import rip.dsp; + import rip.io; + import rip.processing; + import rip.vision; +} diff --git a/source/rip/processing/bitPlane.d b/source/rip/processing/bitPlane.d new file mode 100644 index 0000000..2d215a2 --- /dev/null +++ b/source/rip/processing/bitPlane.d @@ -0,0 +1,102 @@ +module rip.processing.bitPlane; + +private { + import std.algorithm; + import std.stdio; + + import rip.concepts; + import rip.concepts.channel; +} + +pure T getBitMask(T)(ubyte bitIndex) { + //Берём еденицу и сдвигаем её на bitIndex бит влево + //получаем 2 в степени bitIndex + return cast(T) (1 << bitIndex); +} + +pure T getInvertBitMask(T) (ubyte bitIndex) { + return ~getBitMask!(T) (bitIndex); +} + +bool getBit(T) (T value, ubyte index) { + return cast(bool)(value & getBitMask!T(index)); +} + +auto generateBitRange(PixelRange) (PixelRange pixels, Channel channel, + ubyte bitIndex) { + auto range = pixels + .map!((a) => channel.getValue(a).getBit(bitIndex)); + + return range; +} + +auto generateBitRange(Surface surface, Channel channel, ubyte index) { + return surface + //FIXME: Не работает через createFences + .getPixelsRange + .generateBitRange(channel, index); +} + +auto drawBitRange(BitRange)(BitRange range) { + return range + .map!(a => a ? new RGBColor(255, 255, 255) : new RGBColor(0, 0, 0)); +} + +enum BitOperation { + AND = "&", + OR = "|", + NOT = "~", + XOR = "^" +} + +auto cutBitPlane(Range) (Range range, Channel channel, ubyte bitIndex) { + ubyte mask = getInvertBitMask!ubyte(bitIndex); + + alias appMask = (in color) { + ubyte value = cast(ubyte)channel.getValue(color); + ubyte newValue = value & mask; + + return channel.injectValue(color, newValue); + }; + + auto newRange = range + .map!appMask; + + return newRange; +} + +auto injectBitPlane(BitRange, PixelRange) (PixelRange pixels, BitRange bits, + Channel channel, ubyte bitIndex) { + + alias appMask = (pair) { + + auto color = pair[1]; + ubyte channelValue = cast(ubyte)channel.getValue(color); + + ubyte mask; + ubyte newValue; + bool _bit = pair[0]; + + if(_bit == true) { + mask = getBitMask!ubyte(bitIndex); + newValue = channelValue | mask; + } + else { + mask = getInvertBitMask!ubyte(bitIndex); + newValue = channelValue & mask; + } + + return channel.injectValue(color, newValue); + }; + + return zip(bits, pixels).map!appMask; +} + +auto processBitPlane(BitOperation op, Range1, Range2) + (Range1 range1, Range2 range2) { + + mixin( + "return zip(range1, range2) + .map!((pair) => pair[0] " ~ op ~" pair[1]);" + ); +} diff --git a/source/rip/processing/colorization.d b/source/rip/processing/colorization.d new file mode 100644 index 0000000..b2c40d7 --- /dev/null +++ b/source/rip/processing/colorization.d @@ -0,0 +1,80 @@ +module rip.processing.colorization; + +private +{ + import std.algorithm; + import std.range; + + import rip.concepts.ranges; + import rip.concepts.color; + import rip.concepts.surface; +} + +auto compareColors(RGBColor a, RGBColor b) +{ + return a.distance!float(b); +} + +// выборочная замена цвета (впоследстивие можно обобщить) +auto selectiveReplacing(Range)(Range r, RGBColor color, float detalizationLevel) +{ + // процедура замены цвета + auto replacingProcedure(RGBColor a, RGBColor b) + { + if (compareColors(a, b) <= detalizationLevel) + { + return a; + } + else + { + auto I = 0.2126 * a.red!float + 0.7152 * a.green!float + 0.0722 * a.blue!float; + return new RGBColor(I, I, I); + } + } + + auto range = map!(a => replacingProcedure(a, color))(r).array; + return createPixels(range); +} + + +// замена цвета на другой +auto colorReplacing(Range)(Range r, RGBColor color, float detalizationLevel) +{ + // процедура замены цвета + auto replacingProcedure(RGBColor a, RGBColor b) + { + if (compareColors(a, b) <= detalizationLevel) + { + return b; + } + else + { + return a; + } + } + + auto range = map!(a => replacingProcedure(a, color))(r).array; + return createPixels(range); +} + +// простая замена одного цвета другим +auto simpleColorizing(Surface surface, RGBColor color, float detalizationLevel) +{ + auto image = surface + .createFences(1,1) + .map!(a => a.front) + .colorReplacing(color, detalizationLevel) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} + +// выборочное обесцвечивание +auto selectiveColorizing(Surface surface, RGBColor color, float detalizationLevel) +{ + auto image = surface + .createFences(1,1) + .map!(a => a.front) + .selectiveReplacing(color, detalizationLevel) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} diff --git a/source/rip/processing/convolution.d b/source/rip/processing/convolution.d new file mode 100644 index 0000000..69450ae --- /dev/null +++ b/source/rip/processing/convolution.d @@ -0,0 +1,61 @@ +module rip.processing.convolution; + +private +{ + import std.algorithm; + import std.math; + import std.range; + + import rip.concepts.color; + import rip.concepts.ranges; + import rip.concepts.surface; + import rip.processing.filters.linear; +} + +// Точечная свертка +auto elementaryConvolution(Signal, Filter)(Signal signal, Filter filter) +{ + + RGBColor result = new RGBColor(0, 0, 0); + + // Функциональный литерал + alias conv = (pair) + { + if(pair[1] < 0) + { + result -= pair[0] * abs(pair[1]); + } + else + { + result += pair[0] * pair[1]; + } + }; + + foreach (pair; zip(signal, filter)) { + conv(pair); + } + + return result; +} + +// Свертка сигнала с линейным фильтром +auto convolution(Signal)(Signal signal, LinearFilter filter) +{ + // Выполнение свертки на одном фрагменте диапазона окрестностей + auto performConvolution(Signal)(Signal signal, LinearFilter filter) + { + return filter.getOffset + filter.getDivider * elementaryConvolution(signal, filter.getKernel); + } + + auto convolutionRange = map!(a => performConvolution(a, filter))(signal).array; + return convolutionRange; +} + +auto convolve(Surface surface, LinearFilter filter) +{ + auto image = surface + .createFences(filter.getWidth, filter.getHeight) + .convolution(filter) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} diff --git a/source/rip/processing/filters/area.d b/source/rip/processing/filters/area.d new file mode 100644 index 0000000..dfd9791 --- /dev/null +++ b/source/rip/processing/filters/area.d @@ -0,0 +1,46 @@ +module rip.processing.filters.area; + +private +{ + import rip.processing.filters.linear; +} + + +class Area4Filter : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 0.0f, 1.0f, 0.0f ], + [ 1.0f, 1.0f, 1.0f ], + [ 0.0f, 1.0f, 0.0f ], + ] + ); + } +} + + +class Area8Filter: LinearFilter +{ + this() + { + apertureWidth = 5; + apertureHeight = 5; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [0.0f, 0.0f, 1.0f, 0.0f, 0.0f ], + [0.0f, 0.0f, 1.0f, 0.0f, 0.0f ], + [1.0f, 1.0f, 1.0f, 1.0f, 1.0f ], + [0.0f, 0.0f, 1.0f, 0.0f, 0.0f ], + [0.0f, 0.0f, 1.0f, 0.0f, 0.0f ], + ] + ); + } +} diff --git a/source/rip/processing/filters/box.d b/source/rip/processing/filters/box.d new file mode 100644 index 0000000..016b23c --- /dev/null +++ b/source/rip/processing/filters/box.d @@ -0,0 +1,34 @@ +module rip.processing.filters.box; + +private +{ + import rip.processing.filters.linear; + + float[] createBoxKernel(int width, int height) + { + float[] boxKernel; + + foreach (i; 0 .. width) + { + foreach(j; 0 .. height) + { + boxKernel ~= 1.0f; + } + } + + return boxKernel; + } +} + + +class BoxFilter : LinearFilter +{ + this(int filterWidth, int filterHeight) + { + apertureWidth = filterWidth; + apertureHeight = filterHeight; + apertureDivider = 1.0f / (filterWidth * filterHeight); + apertureOffset = 0.0f; + flattenKernel = createBoxKernel(filterWidth, filterHeight); + } +} diff --git a/source/rip/processing/filters/clarity.d b/source/rip/processing/filters/clarity.d new file mode 100644 index 0000000..678bbd7 --- /dev/null +++ b/source/rip/processing/filters/clarity.d @@ -0,0 +1,44 @@ +module rip.processing.filters.clarity; + +private +{ + import rip.processing.filters.linear; +} + + +class ClarityFilter : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ -1.0f, -1.0f, -1.0f ], + [ -1.0f, 9.0f, -1.0f ], + [ -1.0f, -1.0f, -1.0f ], + ] + ); + } +} + + +class ClarityFilter2 : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ -.1f, -.1f, -.1f ], + [ -.1f, 2.0f, -.1f ], + [ -.1f, -.1f, -.1f ], + ] + ); + } +} diff --git a/source/rip/processing/filters/directions.d b/source/rip/processing/filters/directions.d new file mode 100644 index 0000000..cd748b3 --- /dev/null +++ b/source/rip/processing/filters/directions.d @@ -0,0 +1,22 @@ +module rip.processing.filters.directions; + +enum CartesianDirection +{ + X, + Y, + XY +} + + +enum CardinalDirection +{ + NORTH, + NORTH_WEST, + WEST, + SOUTH_WEST, + SOUTH, + SOUTH_EAST, + EAST, + NORTH_EAST +} + diff --git a/source/rip/processing/filters/gaussian.d b/source/rip/processing/filters/gaussian.d new file mode 100644 index 0000000..a989ce4 --- /dev/null +++ b/source/rip/processing/filters/gaussian.d @@ -0,0 +1,25 @@ +module rip.processing.filters.gaussian; + +private +{ + import rip.processing.filters.linear; +} + + +class GaussianBlur : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f / 16.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 1.0f, 2.0f, 1.0f ], + [ 2.0f, 4.0f, 2.0f ], + [ 1.0f, 2.0f, 1.0f ], + ] + ); + } +} diff --git a/source/rip/processing/filters/identity.d b/source/rip/processing/filters/identity.d new file mode 100644 index 0000000..d0eee30 --- /dev/null +++ b/source/rip/processing/filters/identity.d @@ -0,0 +1,25 @@ +module rip.processing.filters.identity; + +private +{ + import rip.processing.filters.linear; +} + + +class IdentityOperator : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 0.0f, 0.0f, 0.0f ], + [ 0.0f, 1.0f, 0.0f ], + [ 0.0f, 0.0f, 0.0f ], + ] + ); + } +} diff --git a/source/rip/processing/filters/kirsch.d b/source/rip/processing/filters/kirsch.d new file mode 100644 index 0000000..460c630 --- /dev/null +++ b/source/rip/processing/filters/kirsch.d @@ -0,0 +1,102 @@ +module rip.processing.filters.kirsch; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; +} + + +class KirschFilter : LinearFilter +{ + this(CardinalDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CardinalDirection) + { + case NORTH: + flattenKernel = flatten( + [ + [ -3.0f, -3.0f, 5.0f ], + [ -3.0f, 0.0f, 5.0f ], + [ -3.0f, 3.0f, 5.0f ], + ] + ); + break; + + case NORTH_WEST: + flattenKernel = flatten( + [ + [ -3.0f, 5.0f, 5.0f ], + [ -3.0f, 0.0f, 5.0f ], + [ -3.0f, -3.0f, -3.0f ], + ] + ); + break; + + case WEST: + flattenKernel = flatten( + [ + [ 5.0f, 5.0f, 5.0f ], + [ -3.0f, 0.0f, -3.0f ], + [ -3.0f, -3.0f, -3.0f ], + ] + ); + break; + + case SOUTH_WEST: + flattenKernel = flatten( + [ + [ 5.0f, 5.0f, -3.0f ], + [ 5.0f, 0.0f, -3.0f ], + [ -3.0f, -3.0f, -3.0f ], + ] + ); + break; + + case SOUTH: + flattenKernel = flatten( + [ + [ 5.0f, -3.0f, -3.0f ], + [ 5.0f, 0.0f, -3.0f ], + [ 5.0f, -3.0f, -3.0f ], + ] + ); + break; + + case SOUTH_EAST: + flattenKernel = flatten( + [ + [ -3.0f, -3.0f, -3.0f ], + [ 5.0f, 0.0f, -3.0f ], + [ 5.0f, 5.0f, -3.0f ], + ] + ); + break; + + case EAST: + flattenKernel = flatten( + [ + [ -3.0f, -3.0f, -3.0f ], + [ -3.0f, 0.0f, -3.0f ], + [ 5.0f, 5.0f, 5.0f ], + ] + ); + break; + + case NORTH_EAST: + flattenKernel = flatten( + [ + [ -3.0f, -3.0f, -3.0f ], + [ -3.0f, 0.0f, 5.0f ], + [ -3.0f, 5.0f, 5.0f ], + ] + ); + break; + } + } +} \ No newline at end of file diff --git a/source/rip/processing/filters/laplace.d b/source/rip/processing/filters/laplace.d new file mode 100644 index 0000000..97d7b3e --- /dev/null +++ b/source/rip/processing/filters/laplace.d @@ -0,0 +1,25 @@ +module rip.processing.filters.laplace; + +private +{ + import rip.processing.filters.linear; +} + + +class LaplaceOperator : LinearFilter +{ + this() + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + flattenKernel = flatten( + [ + [ 0.0f, 1.0f, 0.0f ], + [ 1.0f, -4.0f, 1.0f ], + [ 0.0f, 1.0f, 0.0f ], + ] + ); + } +} diff --git a/source/rip/processing/filters/linear.d b/source/rip/processing/filters/linear.d new file mode 100644 index 0000000..2532f5b --- /dev/null +++ b/source/rip/processing/filters/linear.d @@ -0,0 +1,61 @@ +module rip.processing.filters.linear; + +// Предок всех сверточных фильтров +abstract class LinearFilter +{ + protected + { + float[] flattenKernel; + float apertureWidth, apertureHeight, apertureDivider, apertureOffset; + } + + + @property + { + float getWidth() + { + return apertureWidth; + } + + + float getHeight() + { + return apertureWidth; + } + + + float getDivider() + { + return apertureDivider; + } + + + float getOffset() + { + return apertureOffset; + } + + + float[] getKernel() + { + return flattenKernel; + } + } +} + + +// Преобразование двумерного массива в плоский одномерный +T[] flatten(T)(T[][] dataArray) +{ + T[] intermediateArray; + + foreach (i; 0 .. dataArray.length) + { + foreach (j; 0 .. dataArray[i].length) + { + intermediateArray ~= dataArray[i][j]; + } + } + + return intermediateArray; +} diff --git a/source/rip/processing/filters/package.d b/source/rip/processing/filters/package.d new file mode 100644 index 0000000..ef2aa21 --- /dev/null +++ b/source/rip/processing/filters/package.d @@ -0,0 +1,20 @@ +module rip.processing.filters; + +public { + import rip.processing.filters.directions; + import rip.processing.filters.linear; + + + import rip.processing.filters.area; + import rip.processing.filters.box; + import rip.processing.filters.clarity; + import rip.processing.filters.gaussian; + import rip.processing.filters.identity; + import rip.processing.filters.kirsch; + import rip.processing.filters.laplace; + import rip.processing.filters.prewitt; + import rip.processing.filters.roberts; + import rip.processing.filters.robinson; + import rip.processing.filters.scharr; + import rip.processing.filters.sobel; +} \ No newline at end of file diff --git a/source/rip/processing/filters/prewitt.d b/source/rip/processing/filters/prewitt.d new file mode 100644 index 0000000..7b5762c --- /dev/null +++ b/source/rip/processing/filters/prewitt.d @@ -0,0 +1,51 @@ +module rip.processing.filters.prewitt; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; + +} + + +class PrewittOperator : LinearFilter +{ + this(CartesianDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CartesianDirection) + { + case X: + flattenKernel = flatten( + [ + [ -1.0f, 0.0f, 1.0f ], + [ -1.0f, 0.0f, 1.0f ], + [ -1.0f, 0.0f, 1.0f ], + ] + ); + break; + case Y: + flattenKernel = flatten( + [ + [ 1.0f, 1.0f, 1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -1.0f, -1.0f, -1.0f ], + ] + ); + break; + case XY: + flattenKernel = flatten( + [ + [ 1.0f, 0.0f, 1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ 1.0f, 0.0f, -1.0f ], + ] + ); + break; + } + } +} \ No newline at end of file diff --git a/source/rip/processing/filters/roberts.d b/source/rip/processing/filters/roberts.d new file mode 100644 index 0000000..a651959 --- /dev/null +++ b/source/rip/processing/filters/roberts.d @@ -0,0 +1,48 @@ +module rip.processing.filters.roberts; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; +} + + +class RobertsOperator : LinearFilter +{ + this(CartesianDirection direction) + { + apertureWidth = 2; + apertureHeight = 2; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CartesianDirection) + { + case X: + flattenKernel = flatten( + [ + [ -1.0f, 0.0f ], + [ 0.0f, 1.0f ], + ] + ); + break; + case Y: + flattenKernel = flatten( + [ + [ 0.0f, -1.0f ], + [ 1.0f, 0.0f ], + ] + ); + break; + case XY: + flattenKernel = flatten( + [ + [ 0.0f, 0.0f ], + [ 0.0f, 0.0f ], + ] + ); + break; + } + } +} + diff --git a/source/rip/processing/filters/robinson.d b/source/rip/processing/filters/robinson.d new file mode 100644 index 0000000..32a9f4e --- /dev/null +++ b/source/rip/processing/filters/robinson.d @@ -0,0 +1,102 @@ +module rip.processing.filters.robinson; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; +} + + +class RobinsonOperator : LinearFilter +{ + this(CardinalDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CardinalDirection) + { + case NORTH: + flattenKernel = flatten( + [ + [ -1.0f, 0.0f, 1.0f ], + [ -2.0f, 0.0f, 2.0f ], + [ -1.0f, 0.0f, 1.0f ], + ] + ); + break; + + case NORTH_WEST: + flattenKernel = flatten( + [ + [ 0.0f, 1.0f, 2.0f ], + [ -1.0f, 0.0f, 1.0f ], + [ -2.0f, -1.0f, 0.0f ], + ] + ); + break; + + case WEST: + flattenKernel = flatten( + [ + [ 1.0f, 2.0f, 1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -1.0f, -2.0f, -1.0f ], + ] + ); + break; + + case SOUTH_WEST: + flattenKernel = flatten( + [ + [ 2.0f, 1.0f, 0.0f ], + [ 1.0f, 0.0f, -1.0f ], + [ 0.0f, -1.0f, -2.0f ], + ] + ); + break; + + case SOUTH: + flattenKernel = flatten( + [ + [ 1.0f, 0.0f, -1.0f ], + [ 2.0f, 0.0f, -2.0f ], + [ 1.0f, 0.0f, -1.0f ], + ] + ); + break; + + case SOUTH_EAST: + flattenKernel = flatten( + [ + [ 0.0f, -1.0f, -2.0f ], + [ 1.0f, 0.0f, -1.0f ], + [ 2.0f, 1.0f, 0.0f ], + ] + ); + break; + + case EAST: + flattenKernel = flatten( + [ + [ -1.0f, -2.0f, -1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ 1.0f, 2.0f, 1.0f ], + ] + ); + break; + + case NORTH_EAST: + flattenKernel = flatten( + [ + [ -2.0f, -1.0f, 0.0f ], + [ -1.0f, 0.0f, 1.0f ], + [ 0.0f, 1.0f, 2.0f ], + ] + ); + break; + } + } +} \ No newline at end of file diff --git a/source/rip/processing/filters/scharr.d b/source/rip/processing/filters/scharr.d new file mode 100644 index 0000000..92060dd --- /dev/null +++ b/source/rip/processing/filters/scharr.d @@ -0,0 +1,50 @@ +module rip.processing.filters.scharr; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; +} + + +class ScharrOperator : LinearFilter +{ + this(CartesianDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CartesianDirection) + { + case X: + flattenKernel = flatten( + [ + [ 3.0f, 0.0f, -3.0f ], + [ 10.0f, 0.0f, -10.0f ], + [ 3.0f, 0.0f, -3.0f ], + ] + ); + break; + case Y: + flattenKernel = flatten( + [ + [ 3.0f, 10.0f, 3.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -3.0f, -10.0f, -3.0f ], + ] + ); + break; + case XY: + flattenKernel = flatten( + [ + [ 9.0f, 0.0f, -9.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -9.0f, -0.0f, 9.0f ], + ] + ); + break; + } + } +} diff --git a/source/rip/processing/filters/sobel.d b/source/rip/processing/filters/sobel.d new file mode 100644 index 0000000..7d3b983 --- /dev/null +++ b/source/rip/processing/filters/sobel.d @@ -0,0 +1,93 @@ +module rip.processing.filters.sobel; + +private +{ + import rip.processing.filters.directions; + import rip.processing.filters.linear; +} + + +class SobelOperator : LinearFilter +{ + this(CartesianDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CartesianDirection) + { + case X: + flattenKernel = flatten( + [ + [ 1.0f, 0.0f, -1.0f ], + [ 2.0f, 0.0f, -2.0f ], + [ 1.0f, 0.0f, -1.0f ], + ] + ); + break; + case Y: + flattenKernel = flatten( + [ + [ 1.0f, 2.0f, 1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -1.0f, -2.0f, -1.0f ], + ] + ); + break; + case XY: + flattenKernel = flatten( + [ + [ 1.0f, 0.0f, -1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -1.0f, 0.0f, 1.0f ], + ] + ); + break; + } + } +} + + +class AlternativeSobel : LinearFilter +{ + this(CartesianDirection direction) + { + apertureWidth = 3; + apertureHeight = 3; + apertureDivider = 1.0f; + apertureOffset = 0.0f; + + final switch (direction) with (CartesianDirection) + { + case X: + flattenKernel = flatten( + [ + [ -1.0f, 0.0f, 1.0f ], + [ -2.0f, 0.0f, 2.0f ], + [ -1.0f, 0.0f, 1.0f ], + ] + ); + break; + case Y: + flattenKernel = flatten( + [ + [ -1.0f, -2.0f, -1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ 1.0f, 2.0f, 1.0f ], + ] + ); + break; + case XY: + flattenKernel = flatten( + [ + [ 1.0f, 0.0f, -1.0f ], + [ 0.0f, 0.0f, 0.0f ], + [ -1.0f, 0.0f, 1.0f ], + ] + ); + break; + } + } +} diff --git a/source/rip/processing/grayscale.d b/source/rip/processing/grayscale.d new file mode 100644 index 0000000..68aac50 --- /dev/null +++ b/source/rip/processing/grayscale.d @@ -0,0 +1,60 @@ +module rip.processing.grayscale; + +private +{ + import std.algorithm; + import std.range; + + import rip.concepts.ranges; + import rip.concepts.color; + import rip.concepts.surface; +} + +enum GrayPalette +{ + STANDART, + LUMINANCE, + AVERAGE +} + +auto toGrayScale(Range)(Range r, GrayPalette palette = GrayPalette.STANDART) + //if (is(ElementType!Range == RGBColor)) +{ + RGBColor delegate(RGBColor) grayFunction; + + final switch (palette) with (GrayPalette) + { + case STANDART: + grayFunction = delegate(RGBColor color) { + auto intensity = ((RGBColor color) => 0.2126 * color.red!float + 0.7152 * color.green!float + 0.0722 * color.blue!float); + return new RGBColor(intensity(color), intensity(color), intensity(color)); + }; + break; + + case LUMINANCE: + grayFunction = delegate(RGBColor color) { + auto intensity = color.luminance!float; + return new RGBColor(intensity, intensity, intensity); + }; + break; + + case AVERAGE: + grayFunction = delegate(RGBColor color) { + auto intensity = (color.red!float + color.green!float + color.blue!float) / 3.0; + return new RGBColor(intensity, intensity, intensity); + }; + break; + } + auto range = map!(a => grayFunction(a))(r).array; + return createPixels(range); +} + +auto toGrayScale(Surface surface, GrayPalette palette = GrayPalette.STANDART) +{ + auto image = surface + .createFences(1,1) + .map!(a => a.front) + .toGrayScale(palette) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} diff --git a/source/rip/processing/negative.d b/source/rip/processing/negative.d new file mode 100644 index 0000000..ab653c9 --- /dev/null +++ b/source/rip/processing/negative.d @@ -0,0 +1,28 @@ +module rip.processing.negative; + +private +{ + import std.algorithm; + import std.range; + + import rip.concepts.ranges; + import rip.concepts.color; + import rip.concepts.surface; +} + +auto toNegative(Range)(Range r, RGBColor color = new RGBColor(255, 255, 255)) +{ + auto range = map!(a => color - a)(r).array; + return createPixels(range); +} + + +auto toNegative(Surface surface, RGBColor color = new RGBColor(255, 255, 255)) +{ + auto image = surface + .createFences(1,1) + .map!(a => a.front) + .toNegative(color) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} diff --git a/source/rip/processing/order.d b/source/rip/processing/order.d new file mode 100644 index 0000000..7e37393 --- /dev/null +++ b/source/rip/processing/order.d @@ -0,0 +1,23 @@ +module rip.processing.order; + +private { + import std.range; + import std.algorithm; + + import rip.concepts; + import rip.processing.orderFilters; +} + +/*auto orderFilter(Range) (Range range, OrderFilter filter) { + +}*/ + +auto orderFilter(Surface surface, OrderFilter filter) { + auto newSurface = + surface + .createFences(filter.width, filter.height) + .map!(a => filter.processFence(a)) + .toSurface(surface.getWidth!uint, surface.getHeight!uint); + + return newSurface; +} diff --git a/source/rip/processing/orderFilters/maximum.d b/source/rip/processing/orderFilters/maximum.d new file mode 100644 index 0000000..0db3341 --- /dev/null +++ b/source/rip/processing/orderFilters/maximum.d @@ -0,0 +1,25 @@ +module rip.processing.orderFilters.maximum; + +private { + import std.algorithm; + + import rip.processing.orderFilters; + import rip.concepts; +} + +class MaximumFilter : OrderFilter { + this(uint width, uint height){ + super(width, height); + } + + override RGBColor getValue(RGBColor[] colors) { + return colors[width * height - 1]; + } + + override bool compare(RGBColor a, RGBColor b) { + auto luminance1 = a.luminance!float; + auto luminance2 = b.luminance!float; + + return (luminance1 < luminance2); + } +} diff --git a/source/rip/processing/orderFilters/median.d b/source/rip/processing/orderFilters/median.d new file mode 100644 index 0000000..4c5e3dd --- /dev/null +++ b/source/rip/processing/orderFilters/median.d @@ -0,0 +1,25 @@ +module rip.processing.orderFilters.median; + +private { + import std.algorithm; + + import rip.processing.orderFilters; + import rip.concepts; +} + +class MedianFilter : OrderFilter { + this(uint width, uint height){ + super(width, height); + } + + override RGBColor getValue(RGBColor[] range) { + return range[(width + height)/2]; + } + + override bool compare(RGBColor a, RGBColor b) { + auto luminance1 = a.luminance!float; + auto luminance2 = b.luminance!float; + + return (luminance1 > luminance2); + } +} diff --git a/source/rip/processing/orderFilters/middlePoint.d b/source/rip/processing/orderFilters/middlePoint.d new file mode 100644 index 0000000..a2a5abc --- /dev/null +++ b/source/rip/processing/orderFilters/middlePoint.d @@ -0,0 +1,25 @@ +module rip.processing.orderFilters.middlePoint; + +private { + import std.algorithm; + + import rip.processing.orderFilters; + import rip.concepts; +} + +class MiddlePointFilter : OrderFilter { + this(uint width, uint height){ + super(width, height); + } + + override RGBColor getValue(RGBColor[] range) { + return range[0] - range[$ - 1]; + } + + override bool compare(RGBColor a, RGBColor b) { + auto luminance1 = a.luminance!float; + auto luminance2 = b.luminance!float; + + return (luminance1 > luminance2); + } +} diff --git a/source/rip/processing/orderFilters/minimum.d b/source/rip/processing/orderFilters/minimum.d new file mode 100644 index 0000000..fe5531f --- /dev/null +++ b/source/rip/processing/orderFilters/minimum.d @@ -0,0 +1,25 @@ +module rip.processing.orderFilters.minimum; + +private { + import std.algorithm; + + import rip.processing.orderFilters; + import rip.concepts; +} + +class MinimumFilter : OrderFilter { + this(uint width, uint height){ + super(width, height); + } + + override RGBColor getValue(RGBColor[] range) { + return range[0]; + } + + override bool compare(RGBColor a, RGBColor b) { + auto luminance1 = a.luminance!float; + auto luminance2 = b.luminance!float; + + return (luminance1 < luminance2); + } +} diff --git a/source/rip/processing/orderFilters/orderFilter.d b/source/rip/processing/orderFilters/orderFilter.d new file mode 100644 index 0000000..ddb31e7 --- /dev/null +++ b/source/rip/processing/orderFilters/orderFilter.d @@ -0,0 +1,29 @@ +module rip.processing.orderFilters.orderFilter; + +private { + import std.algorithm; + import std.range; + + import rip.concepts; +} + +abstract class OrderFilter { +public: + uint width, height; + + this(uint width, uint height){ + this.width = width; + this.height = height; + } + + RGBColor getValue(RGBColor[] range); + bool compare(RGBColor a, RGBColor b); + + auto processFence(Range)(Range r) { + auto sorted = (r.array) + .sort!((a, b) => compare(a, b)) + .array; + + return getValue(sorted); + } +} diff --git a/source/rip/processing/orderFilters/package.d b/source/rip/processing/orderFilters/package.d new file mode 100644 index 0000000..11b6001 --- /dev/null +++ b/source/rip/processing/orderFilters/package.d @@ -0,0 +1,12 @@ +module rip.processing.orderFilters; + +public { + import rip.processing.orderFilters.orderFilter; + + import rip.processing.orderFilters.median; + import rip.processing.orderFilters.minimum; + import rip.processing.orderFilters.maximum; + // import rip.processing.orderFilters.middle; + import rip.processing.orderFilters.middlePoint; + //import rip.processing.orderFilters.trimmedMean; +} diff --git a/source/rip/processing/package.d b/source/rip/processing/package.d new file mode 100644 index 0000000..cb82eb8 --- /dev/null +++ b/source/rip/processing/package.d @@ -0,0 +1,8 @@ +module rip.processing; + +public { + import rip.processing.filters; + import rip.processing.grayscale; + import rip.processing.convolution; + import rip.processing.negative; +} diff --git a/source/rip/processing/rough.d b/source/rip/processing/rough.d new file mode 100644 index 0000000..da0e96f --- /dev/null +++ b/source/rip/processing/rough.d @@ -0,0 +1,41 @@ +module rip.processing.rough; + +private +{ + import std.algorithm; + import std.math; + import std.range; + + import rip.concepts; +} + +auto roughPixels(Range, T)(Range r, T numberOfColor) + //if (is(ElementType!Range == RGBColor)) +{ + auto N = cast(ushort) abs(numberOfColor); + RGBColor delegate(RGBColor) roughFunction = delegate(RGBColor color) + { + // assert((0 < numberOfColor) && (numberOfColor <= 255)); + + auto R = color.red!float; + auto G = color.green!float; + auto B = color.blue!float; + + return new RGBColor(R %= N, G %= N, B %= N); + }; + + auto range = map!(a => roughFunction(a))(r).array; + return createPixels(range); +} + +// огрубление (т.е срезание диапазона присутствующих цветов и их оттенков) +auto toRough(T)(Surface surface, T numberOfColors) +{ + auto N = cast(ushort) abs(numberOfColors); + auto image = surface + .createFences(1,1) + .map!(a => a.front) + .roughPixels(numberOfColors) + .toSurface(surface.getWidth!int, surface.getHeight!int); + return image; +} diff --git a/source/rip/vision/package.d b/source/rip/vision/package.d new file mode 100644 index 0000000..47513de --- /dev/null +++ b/source/rip/vision/package.d @@ -0,0 +1 @@ +module rip.vision;