/// , width, height /// /// enum isView(T) = is(typeof(T.init.w) : size_t) && // width is(typeof(T.init.h) : size_t) && // height is(typeof(T.init[0, 0]) ); // color information
/// view alias ViewColor(T) = typeof(T.init[0, 0]);
/// Views /// enum isWritableView(T) = isView!T && is(typeof(T.init[0, 0] = ViewColor!T.init)); /// view /// . /// views "direct views" enum isDirectView(T) = isView!T && is(typeof(T.init.scanline(0)) : ViewColor!T[]);
/// , view /// direct view mixin template DirectView() { alias COLOR = typeof(scanline(0)[0]); /// view[x, y] ref COLOR opIndex(int x, int y) { return scanline(y)[x]; } /// view[x, y] = c COLOR opIndexAssign(COLOR value, int x, int y) { return scanline(y)[x] = value; } }
/// /// struct Image(COLOR) { int w, h; COLOR[] pixels; /// y COLOR[] scanline(int y) { assert(y>=0 && y<h); return pixels[w*y..w*(y+1)]; } mixin DirectView; this(int w, int h) { size(w, h); } /// void size(int w, int h) { this.w = w; this.h = h; if (pixels.length < w*h) pixels.length = w*h; } }
unittest { static assert(isDirectView!(Image!ubyte)); }
/// view, /// template procedural(alias formula) { alias fun = binaryFun!(formula, "x", "y"); alias COLOR = typeof(fun(0, 0)); auto procedural(int w, int h) { struct Procedural { int w, h; auto ref COLOR opIndex(int x, int y) { return fun(x, y); } } return Procedural(w, h); } }
/// view, /// auto solid(COLOR)(COLOR c, int w, int h) { return procedural!((x, y) => c)(w, h); }
/// , view /// view, /// mixin template Warp(V) { V src; auto ref ViewColor!V opIndex(int x, int y) { warp(x, y); return src[x, y]; } static if (isWritableView!V) ViewColor!V opIndexAssign(ViewColor!V value, int x, int y) { warp(x, y); return src[x, y] = value; } }
/// view auto crop(V)(auto ref V src, int x0, int y0, int x1, int y1) if (isView!V) { assert( 0 <= x0 && 0 <= y0); assert(x0 < x1 && y0 < y1); assert(x1 <= src.w && y1 <= src.h); static struct Crop { mixin Warp!V; int x0, y0, x1, y1; @property int w() { return x1-x0; } @property int h() { return y1-y0; } void warp(ref int x, ref int y) { x += x0; y += y0; } static if (isDirectView!V) ViewColor!V[] scanline(int y) { return src.scanline(y0+y)[x0..x1]; } } static assert(isDirectView!V == isDirectView!Crop); return Crop(src, x0, y0, x1, y1); }
/// view . /// Views . void blitTo(SRC, DST)(auto ref SRC src, auto ref DST dst) if (isView!SRC && isWritableView!DST) { assert(src.w == dst.w && src.h == dst.h, "View size mismatch"); foreach (y; 0..src.h) { static if (isDirectView!SRC && isDirectView!DST) dst.scanline(y)[] = src.scanline(y)[]; else { foreach (x; 0..src.w) dst[x, y] = src[x, y]; } } }
/// view /// template warp(string xExpr, string yExpr) { auto warp(V)(auto ref V src) if (isView!V) { static struct Warped { mixin Warp!V; @property int w() { return src.w; } @property int h() { return src.h; } void warp(ref int x, ref int y) { auto nx = mixin(xExpr); auto ny = mixin(yExpr); x = nx; y = ny; } private void testWarpY()() { int y; y = mixin(yExpr); } /// x y /// x, scanlines. static if (xExpr == "x" && __traits(compiles, testWarpY()) && isDirectView!V) ViewColor!V[] scanline(int y) { return src.scanline(mixin(yExpr)); } } return Warped(src); } }
/// view x alias hflip = warp!(q{wx-1}, q{y}); /// view y alias vflip = warp!(q{x}, q{hy-1}); /// view x y alias flip = warp!(q{wx-1}, q{hy-1});
/// view, /// view. template colorMap(alias pred) { alias fun = unaryFun!(pred, false, "c"); auto colorMap(V)(auto ref V src) if (isView!V) { alias OLDCOLOR = ViewColor!V; alias NEWCOLOR = typeof(fun(OLDCOLOR.init)); struct Map { V src; @property int w() { return src.w; } @property int h() { return src.h; } auto ref NEWCOLOR opIndex(int x, int y) { return fun(src[x, y]); } } return Map(src); } }
alias invert = colorMap!q{~c};
/// view /// fun /// . /// , /// , /// vjoin vjoiner. template parallel(alias fun) { auto parallel(V)(auto ref V src, size_t chunkSize = 0) if (isView!V) { auto processSegment(R)(R rows) { auto y0 = rows[0]; auto y1 = y0 + rows.length; auto segment = src.crop(0, y0, src.w, y1); return fun(segment); } import std.range : iota, chunks; import std.parallelism : taskPool, parallel; if (!chunkSize) chunkSize = taskPool.defaultWorkUnitSize(src.h); auto range = src.h.iota.chunks(chunkSize); alias Result = typeof(processSegment(range.front)); auto result = new Result[range.length]; foreach (n; range.length.iota.parallel(1)) result[n] = processSegment(range[n]); return result; } }
import std.algorithm; import std.parallelism; import std.range; import std.stdio; import std.string; import ae.utils.geometry; import ae.utils.graphics.color; import ae.utils.graphics.draw; import ae.utils.graphics.gamma; import ae.utils.graphics.image; void main() { enum W = 4096; const FG = L16(0); const BG = L16(ushort.max); auto image = Image!L16(W, W); image.fill(BG); enum OUTER = W/2 * 16/16; enum INNER = W/2 * 13/16; enum THICK = W/2 * 3/16; image.fillCircle(W/2, W/2, OUTER, FG); image.fillCircle(W/2, W/2, INNER, BG); image.fillRect(0, W/2-INNER, W/2, W/2+INNER, BG); image.fillRect(W/2-THICK/2, W/2-INNER, W/2+THICK/2, W/2+INNER, FG); enum frames = 32; foreach (n; frames.iota.parallel) image .rotate(TAU * n / frames, BG) .copy .downscale!(W/16) .lum2pix(gammaRamp!(ushort, ubyte, ColorSpace.sRGB)) .toPNG .toFile("loading-%02d.png".format(n++)); }
auto mandelbrot(int w, int h) { import std.algorithm, std.range; import ae.utils.graphics.view; return procedural!((x, y) { auto c = (2.0*x/w - 1.5) + (2.0*y/h - 1.0)*1i; return cast(ubyte)(1+ recurrence!((a, n) => c + a[n-1]^^2)(0+0i) .take(ubyte.max) .countUntil!(z => z.re^^2 + z.im^^2 > 4)); })(w, h); } void main() { import std.stdio; import ae.utils.graphics.image; mandelbrot(500, 500).toPNG().toFile("mandel.png"); }
import std.file; import std.path; import std.stdio; import ae.utils.graphics.color; import ae.utils.graphics.gamma; import ae.utils.graphics.image; void main() { alias BGR16 = Color!(ushort, "b", "g", "r"); auto gamma = GammaRamp!(ushort, ubyte)(2.2); foreach (de; dirEntries("input", "*.bmp", SpanMode.shallow)) { static Image!BGR scratch1; static Image!BGR16 scratch2, scratch3; de .read .parseBMP!BGR(scratch1) .parallel!(segment => segment .pix2lum(gamma) .copy(scratch2) .downscale!4(scratch3) .lum2pix(gamma) .copy )(4) .vjoin .toBMP .toFile(buildPath("output-d", de.baseName)); } }
convert \ input/*.bmp \ -depth 16 \ -gamma 0.454545 \ -filter box \ -resize 25% \ -gamma 2.2 \ -depth 8 \ output-im/%02d.bmp
Source: https://habr.com/ru/post/218429/
All Articles