Prepare implementation of blend modes

This commit is contained in:
Elias Batek 2024-05-06 00:48:49 +02:00
parent 44dfc72d78
commit 7c3d511a33
1 changed files with 192 additions and 9 deletions

View File

@ -34,6 +34,12 @@ static assert(Pixel.sizeof == uint.sizeof);
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) {
return rgba(r, g, b, 0xFF);
@ -58,6 +64,13 @@ struct Pixmap {
this.size = size;
}
///
this(int width, int height)
in (width > 0)
in (height > 0) {
this(Size(width, height));
}
///
this(Pixel[] data, int width) @nogc
in (data.length % width == 0) {
@ -231,13 +244,86 @@ private {
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 ====
///
public void alphaBlend(scope Pixel[] target, scope const Pixel[] source) @trusted
in (source.length == target.length) {
foreach (immutable idx, ref pxtarget; target) {
alphaBlend(pxtarget, source.ptr[idx]);
foreach (immutable idx, ref pxTarget; target) {
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 its 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 ====
/++
@ -339,7 +516,7 @@ void drawLine(Pixmap target, Point a, Point b, Pixel color) {
image = source 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;
immutable tRect = OriginRectangle(
@ -367,15 +544,18 @@ void drawPixmap(Pixmap target, Pixmap image, Point pos) {
immutable int drawingWidth = drawingEnd.x - drawingTarget.x;
foreach (y; drawingTarget.y .. drawingEnd.y) {
target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[] =
source.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth);
blendPixels(
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(
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;
foreach (y; drawingTarget.y .. drawingEnd.y) {
target.sliceAt(Point(drawingTarget.x, y), drawingWidth)[]
= sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth);
blendPixels(
target.sliceAt(Point(drawingTarget.x, y), drawingWidth),
sheet.pixmap.sliceAt(Point(drawingSource.x, y + drawingSource.y), drawingWidth),
blend,
);
}
}