March 21, 2014
On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>
> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.

That's really cool. I have some notes though:

 * All the function templates separate the template declaration from the function declaration, when they can just use the function template syntax.

 * Any type is accepted for the colour type, even though certain basic assumptions are made, such as the colour type being assignable to itself.

 * Image.w/h/pixels should probably be read-only properties in the public interface.

 * Any particular reason that coordinate integers are signed? `isView` seems to prefer `size_t`.

 * `Warp` should probably have a `if (isView!V)` constraint.

 * `warp` exclusively uses string expression arguments...

 * I'd recommend sticking to structs (often with AliasThis) over mixins for as far as it goes, as it's much more structured, thus easier to read and reason with.

 * The most common style for template parameter names (Phobos uses it, too) is just `Color`, not `COLOR`.

 * I know "w" and "h" are common abbreviations used in C, but they seem unnecessary in D - detrimental rather - if your code examples are anything to go by.

It's always nice to know examples of high performance range-based code. I noticed that the `View` concept does not have the concept of stride or access to pixels using a single absolute coordinate; maybe if that was the case, the `blitTo` algorithm could be optimized to its conclusion, instead of copying scanlines one by one.
March 21, 2014
On Fri, 21 Mar 2014 11:04:57 +0000, Vladimir Panteleev wrote:

> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-
in-d/
> 
> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.

Good writeup.  Excellent job linking to the Phobos docs and other articles.  I can see people clicking through to "Voldemort Types", etc., making your article a gateway to the world of interesting things that D has to offer.
March 21, 2014
On Friday, 21 March 2014 at 16:21:18 UTC, Jakob Ovrum wrote:
> On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
>> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>>
>> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.
>
> That's really cool. I have some notes though:
>
>  * All the function templates separate the template declaration from the function declaration, when they can just use the function template syntax.

Where this is done, it is on purpose. For example, the "invert" declaration as it appears in the article wouldn't be possible with the shorthand function template syntax.

>  * Any particular reason that coordinate integers are signed?

Yes. I've tried using unsigned coordinates in the past. It was awful.

My conclusion is that if you ever need to subtract values, you should use signed types.

> `isView` seems to prefer `size_t`.

int implicitly casts to size_t but not the other way around.

>  * `warp` exclusively uses string expression arguments...

Yeah. A lambda would need to receive x, y, w and h, and we don't have naryFun in Phobos. (It's there but commented out for some reason.)

To a small extent, I made some compromises for the first iteration for the sake of code looking good for the article, and maybe with the help of feedback improve on that.

For example, a problem I've struggled with is avoiding having two overloads for almost every function in image.d. I've tried multiple approaches: default arguments (in the form of *new Image!COLOR), templates, string mixins, UDAs, pointers, but they all were rather ugly or impractical. Some related compiler issues are 8074, 12386, 12425 and 12426 - fixing those might make some of those approaches more feasible.

>  * I'd recommend sticking to structs (often with AliasThis) over mixins for as far as it goes, as it's much more structured, thus easier to read and reason with.

That doesn't work if you need two-way communication. Although you can pass outer methods as aliases to the inner struct, but then you have to use crazy tricks to make methods of the inner struct static in respect to the inner struct but bound to the outer one via the alias. I've played with this quite a bit for ae.utils.serialization, but I don't think it fits here. Unless I'm missing something?

> It's always nice to know examples of high performance range-based code. I noticed that the `View` concept does not have the concept of stride or access to pixels using a single absolute coordinate; maybe if that was the case, the `blitTo` algorithm could be optimized to its conclusion, instead of copying scanlines one by one.

That's what my previous design used. But ultimately, unless you're dealing with very narrow images, I don't think there will be a noticeable difference in performance. This design is more flexible, though (e.g. vjoiner can serve scanlines from different sources).

Thanks for the feedback.
March 21, 2014
On Friday, 21 March 2014 at 17:03:17 UTC, Vladimir Panteleev wrote:
> Where this is done, it is on purpose. For example, the "invert" declaration as it appears in the article wouldn't be possible with the shorthand function template syntax.

Right, I only skimmed over the various aliases. It looks like only `parallel` does not use such an alias in the article, but as a higher order algorithm it's perfectly justified to use the separated style, especially when all the other higher order algorithms do it.

> Yes. I've tried using unsigned coordinates in the past. It was awful.
>
> My conclusion is that if you ever need to subtract values, you should use signed types.

What happens if negative values sneak into the code? Sounds dangerous.

> Yeah. A lambda would need to receive x, y, w and h, and we don't have naryFun in Phobos. (It's there but commented out for some reason.)
>
> To a small extent, I made some compromises for the first iteration for the sake of code looking good for the article, and maybe with the help of feedback improve on that.

Bleh, I hope we can figure out some notion of lambda equality soon; string lambdas are terrible in so many ways.

> For example, a problem I've struggled with is avoiding having two overloads for almost every function in image.d. I've tried multiple approaches: default arguments (in the form of *new Image!COLOR), templates, string mixins, UDAs, pointers, but they all were rather ugly or impractical. Some related compiler issues are 8074, 12386, 12425 and 12426 - fixing those might make some of those approaches more feasible.

Referring to the overload sets with the `target` parameter?

(Looking at the source I also noticed some `isInputRange` checks are missing; `ElementType` only checks for a `front` property.)

> That doesn't work if you need two-way communication. Although you can pass outer methods as aliases to the inner struct, but then you have to use crazy tricks to make methods of the inner struct static in respect to the inner struct but bound to the outer one via the alias. I've played with this quite a bit for ae.utils.serialization, but I don't think it fits here. Unless I'm missing something?

Aye, to be a sane hierarchy it would need to be redesigned so there are no circular dependencies, which does look rather non-trivial.

> That's what my previous design used. But ultimately, unless you're dealing with very narrow images, I don't think there will be a noticeable difference in performance. This design is more flexible, though (e.g. vjoiner can serve scanlines from different sources).

Maybe parallelized blitting makes sense, though it would really require a use case where blit speed is a bottleneck to matter in the first place.
March 21, 2014
On Friday, 21 March 2014 at 17:24:02 UTC, Jakob Ovrum wrote:
> What happens if negative values sneak into the code? Sounds dangerous.

Well, working with unsigned values correctly complicates the code by a good deal, for one. For example, if you want to draw a circle at (x,y) with radius r, then the first column of the bounding box is max(x-r,0). If you use unsigned coordinates, you have to write x-min(r,x) instead, which is a lot less intuitive. Not to mention, that it makes sense to draw a circle with negative center coordinates (you'll only see the fragment with non-negative coordinates).

There's also tricky bits like if you ever need to subtract a value from another, divide the result, then add something back. With a signed type you can get the expected positive number, even if the number being divided was negative. With an unsigned type, the subtraction can cause an underflow, with the (unsigned) division interpreting the result with a very large positive number instead of a small negative one.

>> For example, a problem I've struggled with is avoiding having two overloads for almost every function in image.d. I've tried multiple approaches: default arguments (in the form of *new Image!COLOR), templates, string mixins, UDAs, pointers, but they all were rather ugly or impractical. Some related compiler issues are 8074, 12386, 12425 and 12426 - fixing those might make some of those approaches more feasible.
>
> Referring to the overload sets with the `target` parameter?

Yes.

> (Looking at the source I also noticed some `isInputRange` checks are missing; `ElementType` only checks for a `front` property.)

Thanks.

>> That's what my previous design used. But ultimately, unless you're dealing with very narrow images, I don't think there will be a noticeable difference in performance. This design is more flexible, though (e.g. vjoiner can serve scanlines from different sources).
>
> Maybe parallelized blitting makes sense, though it would really require a use case where blit speed is a bottleneck to matter in the first place.

I agree, I think in most cases it makes sense to parallelize on a higher level.

Searching the web for "parallel memcpy" seems to confirm my suspicion that it's not practical, at least not for conventional CPUs.
March 21, 2014
On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>
> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.

One more remarks:

Have you considered "infinite" View which, much like infinite InputRanges would lack lacking the w and h property?

It would allow:
   - infinite procedural()
   - support different border-modes when sampling outside of the allowed rectangle (eg. mirror, repeat, clamp_to_edge like OpenGL does with textures). This could be done with a function taking a View and returning an infinite View from it.
   - probably other uses I don't think of
March 21, 2014
On Friday, 21 March 2014 at 18:40:10 UTC, ponce wrote:
> On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
>> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>>
>> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.
>
> One more remarks:
>
> Have you considered "infinite" View which, much like infinite InputRanges would lack lacking the w and h property?
>
> It would allow:
>    - infinite procedural()
>    - support different border-modes when sampling outside of the allowed rectangle (eg. mirror, repeat, clamp_to_edge like OpenGL does with textures). This could be done with a function taking a View and returning an infinite View from it.
>    - probably other uses I don't think of

Erm, so much typos. Corrected.
---
One more remark:

Have you considered an "infinite" View which, much like infinite
InputRanges would lack the w and h properties?

It would allow:
   - infinite procedural() View
   - support for different border-modes when sampling outside of the
allowed rectangle (eg. mirror, repeat, clamp_to_edge like OpenGL
does with textures). This could be done with a function taking a
View and returning an infinite View from it.
   - probably other uses I don't think of.
---
March 21, 2014
On 3/21/2014 4:04 AM, Vladimir Panteleev wrote:
> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>
> Some highlights from a recent overhaul of the graphics package from my D
> library. It makes use of a number of D-specific language features, so I've tried
> to make the article accessible to people new to D as well.

Very, very nice.
March 22, 2014
On Friday, 21 March 2014 at 13:54:25 UTC, Vladimir Panteleev wrote:
> On Friday, 21 March 2014 at 12:27:57 UTC, Rikki Cattermole wrote:
>> On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
>>> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>>>
>>> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.
>>
>> Are you planning on adding a font rasterizer
>
> Parsing vector font files is a big undertaking. I'd likely use a text rendering library, such as FreeType. Hardcoding a small bitmap font for ASCII only is also a possibility.

Yeah it is quite a big one. But it may be well worth it to have it generate code at CTFE per font. Would be more efficient probably.

>> and can it work at CTFE?
>
> This program almost works:
>
> string drawSmiley()
> {
> 	import std.range;
> 	import std.math;
>
> 	import ae.utils.graphics.draw;
> 	import ae.utils.graphics.image;
>
> 	auto smiley = Image!char(20, 20);
> 	smiley.fill(' ');
> 	smiley.fillCircle(10, 10, 10, '#');
> 	smiley.fillCircle( 6, 6, 2, ' ');
> 	smiley.fillCircle(14, 6, 2, ' ');
> 	smiley.fillSector(10, 10, 6, 8, 0, PI, ' ');
>
> 	return smiley.h.iota.map!(y => smiley.scanline(y)).join("\n").idup;
> }
>
> pragma(msg, drawSmiley);
>
> "almost" because fillSector calls atan2, which doesn't work in CTFE. :(
>
> (And yeah, I totally did just declare an image with a

Yeah I've noticed that some of phobos wasn't really designed with CTFE in mind *grumbles*. Maybe one day I'll look into getting that sorted out.
> colorspace of "char".)

March 22, 2014
On Friday, 21 March 2014 at 18:40:10 UTC, ponce wrote:
> On Friday, 21 March 2014 at 11:04:58 UTC, Vladimir Panteleev wrote:
>> http://blog.thecybershadow.net/2014/03/21/functional-image-processing-in-d/
>>
>> Some highlights from a recent overhaul of the graphics package from my D library. It makes use of a number of D-specific language features, so I've tried to make the article accessible to people new to D as well.
>
> One more remarks:
>
> Have you considered "infinite" View which, much like infinite InputRanges would lack lacking the w and h property?
>
> It would allow:
>    - infinite procedural()
>    - support different border-modes when sampling outside of the allowed rectangle (eg. mirror, repeat, clamp_to_edge like OpenGL does with textures). This could be done with a function taking a View and returning an infinite View from it.
>    - probably other uses I don't think of

I've thought about it. Ultimately you'll want to crop it at some point or another, so I think it makes sense if there were operations you'd want to do on an infinite view where cropping will get in the way.

The library has a tiling view, which is infinite in concept but currently in effect it immediately crops the infinite view it creates.

I would probably have to rename the isView template to isFiniteView, and many operations expect a finite view...