## Blending modes.

import chroma, common, math

when defined(amd64) and not defined(pixieNoSimd):
  import nimsimd/sse2

# See https://www.w3.org/TR/compositing-1/
# See https://www.khronos.org/registry/OpenGL/extensions/KHR/KHR_blend_equation_advanced.txt

type
  Blender* = proc(backdrop, source: ColorRGBX): ColorRGBX {.gcsafe, raises: [].}
    ## Function signature returned by blender.
  Masker* = proc(backdrop, source: uint8): uint8 {.gcsafe, raises: [].}
    ## Function signature returned by masker.

when defined(release):
  {.push checks: off.}

proc min(a, b: uint32): uint32 {.inline.} =
  if a < b: a else: b

proc alphaFix(backdrop, source, mixed: ColorRGBA): ColorRGBA =
  ## After mixing an image, adjust its alpha value to be correct.
  let
    sa = source.a.uint32
    ba = backdrop.a.uint32
    t0 = sa * (255 - ba)
    t1 = sa * ba
    t2 = (255 - sa) * ba

  let
    r = t0 * source.r.uint32 + t1 * mixed.r.uint32 + t2 * backdrop.r.uint32
    g = t0 * source.g.uint32 + t1 * mixed.g.uint32 + t2 * backdrop.g.uint32
    b = t0 * source.b.uint32 + t1 * mixed.b.uint32 + t2 * backdrop.b.uint32
    a = sa + ba * (255 - sa) div 255

  if a == 0:
    return

  result.r = (r div a div 255).uint8
  result.g = (g div a div 255).uint8
  result.b = (b div a div 255).uint8
  result.a = a.uint8

proc alphaFix(backdrop, source, mixed: Color): Color =
  ## After mixing an image, adjust its alpha value to be correct.
  result.a = (source.a + backdrop.a * (1.0 - source.a))
  if result.a == 0:
    return

  let
    t0 = source.a * (1 - backdrop.a)
    t1 = source.a * backdrop.a
    t2 = (1 - source.a) * backdrop.a

  result.r = t0 * source.r + t1 * mixed.r + t2 * backdrop.r
  result.g = t0 * source.g + t1 * mixed.g + t2 * backdrop.g
  result.b = t0 * source.b + t1 * mixed.b + t2 * backdrop.b

  result.r /= result.a
  result.g /= result.a
  result.b /= result.a

proc blendAlpha*(backdrop, source: uint8): uint8 {.inline.} =
  ## Blends alphas of backdrop, source.
  source + ((backdrop.uint32 * (255 - source)) div 255).uint8

proc screen(backdrop, source: uint32): uint8 {.inline.} =
  ((backdrop + source).int32 - ((backdrop * source) div 255).int32).uint8

proc hardLight(
  backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint32
): uint8 {.inline.} =
  if sourceColor * 2 <= sourceAlpha:
    ((
      2 * sourceColor * backdropColor +
      (sourceColor * (255 - backdropAlpha)) +
      (backdropColor * (255 - sourceAlpha))
    ) div 255).uint8
  else:
    screen(backdropColor, sourceColor)

proc softLight(backdrop, source: float32): float32 {.inline.} =
  ## Pegtop
  (1 - 2 * source) * backdrop ^ 2 + 2 * source * backdrop

proc `+`(c: Color, v: float32): Color {.inline.} =
  result.r = c.r + v
  result.g = c.g + v
  result.b = c.b + v
  result.a = c.a + v

proc `+`(v: float32, c: Color): Color {.inline.} =
  c + v

proc `*`(c: Color, v: float32): Color {.inline.} =
  result.r = c.r * v
  result.g = c.g * v
  result.b = c.b * v
  result.a = c.a * v

proc `/`(c: Color, v: float32): Color {.inline.} =
  result.r = c.r / v
  result.g = c.g / v
  result.b = c.b / v
  result.a = c.a / v

proc `-`(c: Color, v: float32): Color {.inline.} =
  result.r = c.r - v
  result.g = c.g - v
  result.b = c.b - v
  result.a = c.a - v

proc Lum(C: Color): float32 {.inline.} =
  0.3 * C.r + 0.59 * C.g + 0.11 * C.b

proc ClipColor(C: var Color) {.inline.} =
  let
    L = Lum(C)
    n = min([C.r, C.g, C.b])
    x = max([C.r, C.g, C.b])
  if n < 0:
    C = L + (((C - L) * L) / (L - n))
  if x > 1:
    C = L + (((C - L) * (1 - L)) / (x - L))

proc SetLum(C: Color, l: float32): Color {.inline.} =
  let d = l - Lum(C)
  result.r = C.r + d
  result.g = C.g + d
  result.b = C.b + d
  ClipColor(result)

proc Sat(C: Color): float32 {.inline.} =
  max([C.r, C.g, C.b]) - min([C.r, C.g, C.b])

proc SetSat(C: Color, s: float32): Color {.inline.} =
  let satC = Sat(C)
  if satC > 0:
    result = (C - min([C.r, C.g, C.b])) * s / satC

proc blendNormal*(backdrop, source: ColorRGBX): ColorRGBX =
  if backdrop.a == 0 or source.a == 255:
    return source
  if source.a == 0:
    return backdrop

  let k = (255 - source.a.uint32)
  result.r = source.r + ((backdrop.r.uint32 * k) div 255).uint8
  result.g = source.g + ((backdrop.g.uint32 * k) div 255).uint8
  result.b = source.b + ((backdrop.b.uint32 * k) div 255).uint8
  result.a = blendAlpha(backdrop.a, source.a)

proc blendDarken(backdrop, source: ColorRGBX): ColorRGBX =
  proc blend(
    backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
  ): uint8 {.inline.} =
    min(
      backdropColor + ((255 - backdropAlpha).uint32 * sourceColor) div 255,
      sourceColor + ((255 - sourceAlpha).uint32 * backdropColor) div 255
    ).uint8

  result.r = blend(backdrop.r, backdrop.a, source.r, source.a)
  result.g = blend(backdrop.g, backdrop.a, source.g, source.a)
  result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendMultiply(backdrop, source: ColorRGBX): ColorRGBX =
  proc blend(
    backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
  ): uint8 {.inline.} =
    ((
      (255 - backdropAlpha).uint32 * sourceColor +
      (255 - sourceAlpha).uint32 * backdropColor +
      backdropColor.uint32 * sourceColor
    ) div 255).uint8

  result.r = blend(backdrop.r, backdrop.a, source.r, source.a)
  result.g = blend(backdrop.g, backdrop.a, source.g, source.a)
  result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
  result.a = blendAlpha(backdrop.a, source.a)

# proc blendLinearBurn(backdrop, source: ColorRGBX): ColorRGBX =
#   let
#     backdrop = backdrop.toStraightAlpha()
#     source = source.toStraightAlpha()
#   result.r = min(0, backdrop.r.int32 + source.r.int32 - 255).uint8
#   result.g = min(0, backdrop.g.int32 + source.g.int32 - 255).uint8
#   result.b = min(0, backdrop.b.int32 + source.b.int32 - 255).uint8
#   result = alphaFix(backdrop, source, result)
#   result = result.toPremultipliedAlpha()

proc blendColorBurn(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba()
    source = source.rgba()
  proc blend(backdrop, source: uint32): uint8 {.inline.} =
    if backdrop == 255:
      255.uint8
    elif source == 0:
      0
    else:
      255 - min(255, (255 * (255 - backdrop)) div source).uint8
  var blended: ColorRGBA
  blended.r = blend(backdrop.r, source.r)
  blended.g = blend(backdrop.g, source.g)
  blended.b = blend(backdrop.b, source.b)
  result = alphaFix(backdrop, source, blended).rgbx()

proc blendLighten(backdrop, source: ColorRGBX): ColorRGBX =
  proc blend(
    backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
  ): uint8 {.inline.} =
    max(
      backdropColor + ((255 - backdropAlpha).uint32 * sourceColor) div 255,
      sourceColor + ((255 - sourceAlpha).uint32 * backdropColor) div 255
    ).uint8

  result.r = blend(backdrop.r, backdrop.a, source.r, source.a)
  result.g = blend(backdrop.g, backdrop.a, source.g, source.a)
  result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendScreen(backdrop, source: ColorRGBX): ColorRGBX =
  result.r = screen(backdrop.r, source.r)
  result.g = screen(backdrop.g, source.g)
  result.b = screen(backdrop.b, source.b)
  result.a = blendAlpha(backdrop.a, source.a)

# proc blendLinearDodge(backdrop, source: ColorRGBX): ColorRGBX =
#   let
#     backdrop = backdrop.toStraightAlpha()
#     source = source.toStraightAlpha()
#   result.r = min(backdrop.r.uint32 + source.r, 255).uint8
#   result.g = min(backdrop.g.uint32 + source.g, 255).uint8
#   result.b = min(backdrop.b.uint32 + source.b, 255).uint8
#   result = alphaFix(backdrop, source, result)
#   result = result.toPremultipliedAlpha()

proc blendColorDodge(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba()
    source = source.rgba()
  proc blend(backdrop, source: uint32): uint8 {.inline.} =
    if backdrop == 0:
      0.uint8
    elif source == 255:
      255
    else:
      min(255, (255 * backdrop) div (255 - source)).uint8
  var blended: ColorRGBA
  blended.r = blend(backdrop.r, source.r)
  blended.g = blend(backdrop.g, source.g)
  blended.b = blend(backdrop.b, source.b)
  result = alphaFix(backdrop, source, blended).rgbx()

proc blendOverlay(backdrop, source: ColorRGBX): ColorRGBX =
  result.r = hardLight(source.r, source.a, backdrop.r, backdrop.a)
  result.g = hardLight(source.g, source.a, backdrop.g, backdrop.a)
  result.b = hardLight(source.b, source.a, backdrop.b, backdrop.a)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendSoftLight(backdrop, source: ColorRGBX): ColorRGBX =
  # proc softLight(backdrop, source: int32): uint8 {.inline.} =
  #   ## Pegtop
  #   (
  #     ((255 - 2 * source) * backdrop ^ 2) div 255 ^ 2 +
  #     (2 * source * backdrop) div 255
  #   ).uint8

  let
    backdrop = backdrop.rgba()
    source = source.rgba()

  var rgba: ColorRGBA
  when defined(amd64) and not defined(pixieNoSimd):
    let
      vb = mm_setr_ps(
        backdrop.r.float32,
        backdrop.g.float32,
        backdrop.b.float32,
        0
      )
      vs = mm_setr_ps(source.r.float32, source.g.float32, source.b.float32, 0)
      v2 = mm_set1_ps(2)
      v255 = mm_set1_ps(255)
      v255sq = mm_set1_ps(255 * 255)
      vm = ((v255 - v2 * vs) * vb * vb) / v255sq + (v2 * vs * vb) / v255
      values = cast[array[4, uint32]](mm_cvtps_epi32(vm))

    rgba.r = values[0].uint8
    rgba.g = values[1].uint8
    rgba.b = values[2].uint8

    # proc alphaFix(backdrop, source, mixed: ColorRGBX): ColorRGBX {.inline.} =
    #   if backdrop.a == 0 and source.a == 0:
    #     return
    #   let
    #     vb = mm_setr_ps(backdrop.r.float32, backdrop.g.float32, backdrop.b.float32, 0)
    #     vs = mm_setr_ps(source.r.float32, source.g.float32, source.b.float32, 0)
    #     vm = mm_setr_ps(mixed.r.float32, mixed.g.float32, mixed.b.float32, 0)
    #   alphaFix(backdrop, source, vb, vs, vm)

    let
      sa = source.a.float32
      ba = backdrop.a.float32
      a = sa + ba * (255 - sa) / 255
    if a == 0:
      return

    let
      t0 = mm_set1_ps(sa * (255 - ba))
      t1 = mm_set1_ps(sa * ba)
      t2 = mm_set1_ps((255 - sa) * ba)
      va = mm_set1_ps(a)
      final = cast[array[4, uint32]](
        mm_cvtps_epi32((t0 * vs + t1 * vm + t2 * vb) / va / v255)
      )

    rgba.r = final[0].uint8
    rgba.g = final[1].uint8
    rgba.b = final[2].uint8
    rgba.a = a.uint8
  else:
    let
      b = backdrop.color
      s = source.color
    var blended: Color
    blended.r = softLight(b.r, s.r)
    blended.g = softLight(b.g, s.g)
    blended.b = softLight(b.b, s.b)
    blended = alphaFix(b, s, blended)
    rgba = blended.rgba

  result = rgba.rgbx()

proc blendHardLight(backdrop, source: ColorRGBX): ColorRGBX =
  result.r = hardLight(backdrop.r, backdrop.a, source.r, source.a)
  result.g = hardLight(backdrop.g, backdrop.a, source.g, source.a)
  result.b = hardLight(backdrop.b, backdrop.a, source.b, source.a)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendDifference(backdrop, source: ColorRGBX): ColorRGBX =
  proc blend(
    backdropColor, backdropAlpha, sourceColor, sourceAlpha: uint8
  ): uint8 {.inline.} =
    ((backdropColor + sourceColor).int32 - 2 *
      (min(
        backdropColor.uint32 * sourceAlpha,
        sourceColor.uint32 * backdropAlpha
      ) div 255).int32
    ).uint8

  result.r = blend(backdrop.r, backdrop.a, source.r, source.a)
  result.g = blend(backdrop.g, backdrop.a, source.g, source.a)
  result.b = blend(backdrop.b, backdrop.a, source.b, source.a)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendExclusion(backdrop, source: ColorRGBX): ColorRGBX =
  proc blend(backdrop, source: uint32): uint8 {.inline.} =
    let v = (backdrop + source).int32 - ((2 * backdrop * source) div 255).int32
    max(0, v).uint8
  result.r = blend(backdrop.r.uint32, source.r.uint32)
  result.g = blend(backdrop.g.uint32, source.g.uint32)
  result.b = blend(backdrop.b.uint32, source.b.uint32)
  result.a = blendAlpha(backdrop.a, source.a)

proc blendColor(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba().color
    source = source.rgba().color
    blended = SetLum(source, Lum(backdrop))
  result = alphaFix(backdrop, source, blended).rgba.rgbx()

proc blendLuminosity(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba().color
    source = source.rgba().color
    blended = SetLum(backdrop, Lum(source))
  result = alphaFix(backdrop, source, blended).rgba.rgbx()

proc blendHue(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba().color
    source = source.rgba().color
    blended = SetLum(SetSat(source, Sat(backdrop)), Lum(backdrop))
  result = alphaFix(backdrop, source, blended).rgba.rgbx()

proc blendSaturation(backdrop, source: ColorRGBX): ColorRGBX =
  let
    backdrop = backdrop.rgba().color
    source = source.rgba().color
    blended = SetLum(SetSat(backdrop, Sat(source)), Lum(backdrop))
  result = alphaFix(backdrop, source, blended).rgba.rgbx()

proc blendMask*(backdrop, source: ColorRGBX): ColorRGBX =
  let k = source.a.uint32
  result.r = ((backdrop.r * k) div 255).uint8
  result.g = ((backdrop.g * k) div 255).uint8
  result.b = ((backdrop.b * k) div 255).uint8
  result.a = ((backdrop.a * k) div 255).uint8

proc blendSubtractMask(backdrop, source: ColorRGBX): ColorRGBX =
  let a = (backdrop.a.uint32 * (255 - source.a)) div 255
  result.r = ((backdrop.r * a) div 255).uint8
  result.g = ((backdrop.g * a) div 255).uint8
  result.b = ((backdrop.b * a) div 255).uint8
  result.a = a.uint8

proc blendExcludeMask(backdrop, source: ColorRGBX): ColorRGBX =
  let a = max(backdrop.a, source.a).uint32 - min(backdrop.a, source.a)
  result.r = ((source.r * a) div 255).uint8
  result.g = ((source.g * a) div 255).uint8
  result.b = ((source.b * a) div 255).uint8
  result.a = a.uint8

proc blendOverwrite(backdrop, source: ColorRGBX): ColorRGBX =
  source

# proc blendWhite(backdrop, source: ColorRGBX): ColorRGBX =
#   ## For testing
#   rgbx(255, 255, 255, 255)

proc blender*(blendMode: BlendMode): Blender {.raises: [].} =
  ## Returns a blend function for a given blend mode.
  case blendMode:
  of bmNormal: blendNormal
  of bmDarken: blendDarken
  of bmMultiply: blendMultiply
  # of bmLinearBurn: blendLinearBurn
  of bmColorBurn: blendColorBurn
  of bmLighten: blendLighten
  of bmScreen: blendScreen
  # of bmLinearDodge: blendLinearDodge
  of bmColorDodge: blendColorDodge
  of bmOverlay: blendOverlay
  of bmSoftLight: blendSoftLight
  of bmHardLight: blendHardLight
  of bmDifference: blendDifference
  of bmExclusion: blendExclusion
  of bmHue: blendHue
  of bmSaturation: blendSaturation
  of bmColor: blendColor
  of bmLuminosity: blendLuminosity
  of bmMask: blendMask
  of bmOverwrite: blendOverwrite
  of bmSubtractMask: blendSubtractMask
  of bmExcludeMask: blendExcludeMask

proc maskNormal(backdrop, source: uint8): uint8 =
  ## Blending masks
  blendAlpha(backdrop, source)

proc maskMaskInline*(backdrop, source: uint8): uint8 {.inline.} =
  ## Masking masks
  ((backdrop.uint32 * source) div 255).uint8

proc maskMask(backdrop, source: uint8): uint8 =
  maskMaskInline(backdrop, source)

proc maskSubtract(backdrop, source: uint8): uint8 =
  ((backdrop.uint32 * (255 - source)) div 255).uint8

proc maskExclude(backdrop, source: uint8): uint8 =
  max(backdrop, source) - min(backdrop, source)

proc maskOverwrite(backdrop, source: uint8): uint8 =
  source

proc masker*(blendMode: BlendMode): Masker {.raises: [PixieError].} =
  ## Returns a blend masking function for a given blend masking mode.
  case blendMode:
  of bmNormal: maskNormal
  of bmMask: maskMask
  of bmOverwrite: maskOverwrite
  of bmSubtractMask: maskSubtract
  of bmExcludeMask: maskExclude
  else:
    raise newException(PixieError, "No masker for " & $blendMode)

when defined(amd64) and not defined(pixieNoSimd):
  type
    BlenderSimd* = proc(blackdrop, source: M128i): M128i {.gcsafe, raises: [].}
      ## Function signature returned by blenderSimd.
    MaskerSimd* = proc(blackdrop, source: M128i): M128i {.gcsafe, raises: [].}
      ## Function signature returned by maskerSimd.

  proc blendNormalInlineSimd*(backdrop, source: M128i): M128i {.inline.} =
    let
      alphaMask = mm_set1_epi32(cast[int32](0xff000000))
      oddMask = mm_set1_epi16(cast[int16](0xff00))
      div255 = mm_set1_epi16(cast[int16](0x8081))

    var
      sourceAlpha = mm_and_si128(source, alphaMask)
      backdropEven = mm_slli_epi16(backdrop, 8)
      backdropOdd = mm_and_si128(backdrop, oddMask)

    sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16))

    let k = mm_sub_epi32(
      mm_set1_epi32(cast[int32]([0.uint8, 255, 0, 255])),
      sourceAlpha
    )

    backdropEven = mm_mulhi_epu16(backdropEven, k)
    backdropOdd = mm_mulhi_epu16(backdropOdd, k)

    backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
    backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)

    mm_add_epi8(
      source,
      mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))
    )

  proc blendNormalSimd(backdrop, source: M128i): M128i =
    blendNormalInlineSimd(backdrop, source)

  proc blendMaskInlineSimd*(backdrop, source: M128i): M128i {.inline.} =
    let
      alphaMask = mm_set1_epi32(cast[int32](0xff000000))
      oddMask = mm_set1_epi16(cast[int16](0xff00))
      div255 = mm_set1_epi16(cast[int16](0x8081))

    var
      sourceAlpha = mm_and_si128(source, alphaMask)
      backdropEven = mm_slli_epi16(backdrop, 8)
      backdropOdd = mm_and_si128(backdrop, oddMask)

    sourceAlpha = mm_or_si128(sourceAlpha, mm_srli_epi32(sourceAlpha, 16))

    backdropEven = mm_mulhi_epu16(backdropEven, sourceAlpha)
    backdropOdd = mm_mulhi_epu16(backdropOdd, sourceAlpha)

    backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
    backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)

    mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))

  proc blendMaskSimd(backdrop, source: M128i): M128i =
    blendMaskInlineSimd(backdrop, source)

  proc blendOverwriteSimd(backdrop, source: M128i): M128i =
    source

  proc blenderSimd*(blendMode: BlendMode): BlenderSimd {.raises: [PixieError].} =
    ## Returns a blend function for a given blend mode with SIMD support.
    case blendMode:
    of bmNormal: blendNormalSimd
    of bmMask: blendMaskSimd
    of bmOverwrite: blendOverwriteSimd
    else:
      raise newException(PixieError, "No SIMD blender for " & $blendMode)

  proc hasSimdBlender*(blendMode: BlendMode): bool {.inline, raises: [].} =
    ## Is there a blend function for a given blend mode with SIMD support?
    blendMode in {bmNormal, bmMask, bmOverwrite}

  proc maskNormalInlineSimd*(backdrop, source: M128i): M128i {.inline.} =
    ## Blending masks
    let
      oddMask = mm_set1_epi16(cast[int16](0xff00))
      v255high = mm_set1_epi16(cast[int16](255.uint16 shl 8))
      div255 = mm_set1_epi16(cast[int16](0x8081))

    var
      sourceEven = mm_slli_epi16(source, 8)
      sourceOdd = mm_and_si128(source, oddMask)

    let
      evenK = mm_sub_epi16(v255high, sourceEven)
      oddK = mm_sub_epi16(v255high, sourceOdd)

    var
      backdropEven = mm_slli_epi16(backdrop, 8)
      backdropOdd = mm_and_si128(backdrop, oddMask)

    # backdrop * k
    backdropEven = mm_mulhi_epu16(backdropEven, evenK)
    backdropOdd = mm_mulhi_epu16(backdropOdd, oddK)

    # div 255
    backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
    backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)

    # Shift from high to low bits
    sourceEven = mm_srli_epi16(sourceEven, 8)
    sourceOdd = mm_srli_epi16(sourceOdd, 8)

    let
      blendedEven = mm_add_epi16(sourceEven, backdropEven)
      blendedOdd = mm_add_epi16(sourceOdd, backdropOdd)

    mm_or_si128(blendedEven, mm_slli_epi16(blendedOdd, 8))

  proc maskNormalSimd(backdrop, source: M128i): M128i =
    maskNormalInlineSimd(backdrop, source)

  proc maskMaskInlineSimd*(backdrop, source: M128i): M128i =
    let
      oddMask = mm_set1_epi16(cast[int16](0xff00))
      div255 = mm_set1_epi16(cast[int16](0x8081))
      sourceEven = mm_slli_epi16(source, 8)
      sourceOdd = mm_and_si128(source, oddMask)

    var
      backdropEven = mm_slli_epi16(backdrop, 8)
      backdropOdd = mm_and_si128(backdrop, oddMask)

    # backdrop * source
    backdropEven = mm_mulhi_epu16(backdropEven, sourceEven)
    backdropOdd = mm_mulhi_epu16(backdropOdd, sourceOdd)

    # div 255
    backdropEven = mm_srli_epi16(mm_mulhi_epu16(backdropEven, div255), 7)
    backdropOdd = mm_srli_epi16(mm_mulhi_epu16(backdropOdd, div255), 7)

    mm_or_si128(backdropEven, mm_slli_epi16(backdropOdd, 8))

  proc maskMaskSimd(backdrop, source: M128i): M128i =
    maskMaskInlineSimd(backdrop, source)

  proc maskerSimd*(blendMode: BlendMode): MaskerSimd {.raises: [PixieError].} =
    ## Returns a blend masking function with SIMD support.
    case blendMode:
    of bmNormal: maskNormalSimd
    of bmMask: maskMaskSimd
    of bmOverwrite: blendOverwriteSimd
    else:
      raise newException(PixieError, "No SIMD masker for " & $blendMode)

  proc hasSimdMasker*(blendMode: BlendMode): bool {.inline, raises: [].} =
    ## Is there a blend masking function with SIMD support?
    blendMode in {bmNormal, bmMask, bmOverwrite}

when defined(release):
  {.pop.}
