mirror of https://github.com/adamdruppe/arsd.git
Prepare implementation of blend modes
This commit is contained in:
parent
44dfc72d78
commit
7c3d511a33
201
pixmappaint.d
201
pixmappaint.d
|
@ -34,6 +34,12 @@ static assert(Pixel.sizeof == uint.sizeof);
|
||||||
return Pixel(r, g, b, a);
|
return Pixel(r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
Pixel rgba(ubyte r, ubyte g, ubyte b, float aPct)
|
||||||
|
in (aPct >= 0 && aPct <= 1) {
|
||||||
|
return Pixel(r, g, b, typeCast!ubyte(aPct * 255));
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
Pixel rgb(ubyte r, ubyte g, ubyte b) {
|
Pixel rgb(ubyte r, ubyte g, ubyte b) {
|
||||||
return rgba(r, g, b, 0xFF);
|
return rgba(r, g, b, 0xFF);
|
||||||
|
@ -58,6 +64,13 @@ struct Pixmap {
|
||||||
this.size = size;
|
this.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
this(int width, int height)
|
||||||
|
in (width > 0)
|
||||||
|
in (height > 0) {
|
||||||
|
this(Size(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
this(Pixel[] data, int width) @nogc
|
this(Pixel[] data, int width) @nogc
|
||||||
in (data.length % width == 0) {
|
in (data.length % width == 0) {
|
||||||
|
@ -231,13 +244,86 @@ private {
|
||||||
Point pos(Rectangle r) => r.upperLeft;
|
Point pos(Rectangle r) => r.upperLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Fast 8-bit “percentage” function
|
||||||
|
|
||||||
|
This function optimizes its runtime performance by substituting
|
||||||
|
the division by 255 with an approximation using bitshifts.
|
||||||
|
|
||||||
|
Nonetheless, the its result are as accurate as a floating point
|
||||||
|
division with 64-bit precision.
|
||||||
|
|
||||||
|
Params:
|
||||||
|
nPercentage = percentage as the number of 255ths (“two hundred fifty-fifths”)
|
||||||
|
value = base value (“total”)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
`round(value * nPercentage / 255.0)`
|
||||||
|
+/
|
||||||
|
ubyte n255thsOf(const ubyte nPercentage, const ubyte value) {
|
||||||
|
immutable factor = (nPercentage | (nPercentage << 8));
|
||||||
|
return (((value * factor) + 0x8080) >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@safe unittest {
|
||||||
|
// Accuracy verification
|
||||||
|
|
||||||
|
static ubyte n255thsOfFP64(const ubyte nPercentage, const ubyte value) {
|
||||||
|
import std.math : round;
|
||||||
|
|
||||||
|
return (value * nPercentage / 255.0).round().typeCast!ubyte();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int value = ubyte.min; value <= ubyte.max; ++value) {
|
||||||
|
for (int percent = ubyte.min; percent <= ubyte.max; ++percent) {
|
||||||
|
immutable v = cast(ubyte) value;
|
||||||
|
immutable p = cast(ubyte) percent;
|
||||||
|
|
||||||
|
immutable approximated = n255thsOf(p, v);
|
||||||
|
immutable precise = n255thsOfFP64(p, v);
|
||||||
|
assert(approximated == precise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Sets the opacity of a [Pixmap].
|
||||||
|
|
||||||
|
This lossy operation updates the alpha-channel value of each pixel.
|
||||||
|
→ `alpha *= opacity`
|
||||||
|
|
||||||
|
See_Also:
|
||||||
|
Use [opacityF] with opacity values in percent (%).
|
||||||
|
+/
|
||||||
|
void opacity(ref Pixmap pixmap, const ubyte opacity) {
|
||||||
|
foreach (ref px; pixmap.data) {
|
||||||
|
px.a = opacity.n255thsOf(px.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Sets the opacity of a [Pixmap].
|
||||||
|
|
||||||
|
This lossy operation updates the alpha-channel value of each pixel.
|
||||||
|
→ `alpha *= opacity`
|
||||||
|
|
||||||
|
See_Also:
|
||||||
|
Use [opacity] with 8-bit integer opacity values (in 255ths).
|
||||||
|
+/
|
||||||
|
void opacityF(ref Pixmap pixmap, const float opacity)
|
||||||
|
in (opacity >= 0)
|
||||||
|
in (opacity <= 1.0) {
|
||||||
|
immutable opacity255 = typeCast!ubyte(opacity * 255);
|
||||||
|
pixmap.opacity = opacity255;
|
||||||
|
}
|
||||||
|
|
||||||
// ==== Alpha-blending functions ====
|
// ==== Alpha-blending functions ====
|
||||||
|
|
||||||
///
|
///
|
||||||
public void alphaBlend(scope Pixel[] target, scope const Pixel[] source) @trusted
|
public void alphaBlend(scope Pixel[] target, scope const Pixel[] source) @trusted
|
||||||
in (source.length == target.length) {
|
in (source.length == target.length) {
|
||||||
foreach (immutable idx, ref pxtarget; target) {
|
foreach (immutable idx, ref pxTarget; target) {
|
||||||
alphaBlend(pxtarget, source.ptr[idx]);
|
alphaBlend(pxTarget, source.ptr[idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +341,97 @@ public void alphaBlend(ref Pixel pxTarget, const Pixel pxSource) @trusted {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==== Blending functions ====
|
||||||
|
|
||||||
|
enum BlendMode {
|
||||||
|
none = 0,
|
||||||
|
overwrite = none,
|
||||||
|
normal = 1,
|
||||||
|
alpha = normal,
|
||||||
|
|
||||||
|
multiply,
|
||||||
|
screen,
|
||||||
|
|
||||||
|
darken,
|
||||||
|
lighten,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
alias Blend = BlendMode;
|
||||||
|
|
||||||
|
// undocumented
|
||||||
|
enum blendNormal = BlendMode.normal;
|
||||||
|
|
||||||
|
/++
|
||||||
|
Blends pixel `source` into pixel `target`.
|
||||||
|
+/
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == BlendMode.overwrite) {
|
||||||
|
target = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == Blend.alpha) {
|
||||||
|
return alphaBlend(target, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == Blend.multiply) {
|
||||||
|
function(ref Pixel target, const Pixel source) @trusted {
|
||||||
|
foreach (immutable ib, ref ch; target.components) {
|
||||||
|
ch = n255thsOf(source.components.ptr[ib], ch);
|
||||||
|
}
|
||||||
|
}(target, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == Blend.screen) {
|
||||||
|
assert(false, "TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == Blend.darken) {
|
||||||
|
assert(false, "TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixel(BlendMode mode)(ref Pixel target, const Pixel source) if (mode == Blend.lighten) {
|
||||||
|
assert(false, "TODO");
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Blends the pixel data of `source` into `target`.
|
||||||
|
|
||||||
|
`source` and `target` MUST have the same length.
|
||||||
|
+/
|
||||||
|
void blendPixels(BlendMode mode)(scope Pixel[] target, scope const Pixel[] source) @trusted
|
||||||
|
in (source.length == target.length) {
|
||||||
|
static if (mode == BlendMode.overwrite) {
|
||||||
|
target.ptr[0 .. target.length] = source.ptr[0 .. target.length];
|
||||||
|
} else {
|
||||||
|
// better error message in case it’s not implemented
|
||||||
|
static if (!is(typeof(blendPixel!mode))) {
|
||||||
|
static assert(false, "Missing `blendPixel!(" ~ mode.stringof ~ ")`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (immutable idx, ref pxTarget; target) {
|
||||||
|
blendPixel!mode(pxTarget, source.ptr[idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void blendPixels(scope Pixel[] target, scope const Pixel[] source, BlendMode mode) {
|
||||||
|
import std.meta : NoDuplicates;
|
||||||
|
import std.traits : EnumMembers;
|
||||||
|
|
||||||
|
final switch (mode) with (BlendMode) {
|
||||||
|
static foreach (m; NoDuplicates!(EnumMembers!BlendMode)) {
|
||||||
|
case m:
|
||||||
|
return blendPixels!m(target, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ==== Drawing functions ====
|
// ==== Drawing functions ====
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
@ -339,7 +516,7 @@ void drawLine(Pixmap target, Point a, Point b, Pixel color) {
|
||||||
image = source pixmap
|
image = source pixmap
|
||||||
pos = top-left destination position (on the target pixmap)
|
pos = top-left destination position (on the target pixmap)
|
||||||
+/
|
+/
|
||||||
void drawPixmap(Pixmap target, Pixmap image, Point pos) {
|
void drawPixmap(Pixmap target, Pixmap image, Point pos, Blend blend = blendNormal) {
|
||||||
alias source = image;
|
alias source = image;
|
||||||
|
|
||||||
immutable tRect = OriginRectangle(
|
immutable tRect = OriginRectangle(
|
||||||
|
@ -367,15 +544,18 @@ void drawPixmap(Pixmap target, Pixmap image, Point pos) {
|
||||||
immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
|
immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
|
||||||
|
|
||||||
foreach (y; drawingTarget.y .. drawingEnd.y) {
|
foreach (y; drawingTarget.y .. drawingEnd.y) {
|
||||||
target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[] =
|
blendPixels(
|
||||||
source.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth);
|
target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
|
||||||
|
source.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
|
||||||
|
blend,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
Draws a sprite from a spritesheet
|
Draws a sprite from a spritesheet
|
||||||
+/
|
+/
|
||||||
void drawSprite(Pixmap target, const SpriteSheet sheet, int spriteIndex, Point pos) {
|
void drawSprite(Pixmap target, const SpriteSheet sheet, int spriteIndex, Point pos, Blend blend = blendNormal) {
|
||||||
immutable tRect = OriginRectangle(
|
immutable tRect = OriginRectangle(
|
||||||
Size(target.width, target.height),
|
Size(target.width, target.height),
|
||||||
);
|
);
|
||||||
|
@ -405,7 +585,10 @@ void drawSprite(Pixmap target, const SpriteSheet sheet, int spriteIndex, Point p
|
||||||
immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
|
immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
|
||||||
|
|
||||||
foreach (y; drawingTarget.y .. drawingEnd.y) {
|
foreach (y; drawingTarget.y .. drawingEnd.y) {
|
||||||
target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[]
|
blendPixels(
|
||||||
= sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth);
|
target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
|
||||||
|
sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
|
||||||
|
blend,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue