(function attachPyroBitmapSelection(root) {
  'use strict';

  const finite = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
  const clamp = (value, min, max) => Math.min(max, Math.max(min, value));

  function localPointUnclamped(object, point) {
    const width = Math.max(1, finite(object?.width, 1)); const height = Math.max(1, finite(object?.height, 1));
    const centerX = finite(object?.x) + width / 2; const centerY = finite(object?.y) + height / 2;
    const angle = -finite(object?.rotation) * Math.PI / 180; const dx = finite(point?.x) - centerX; const dy = finite(point?.y) - centerY;
    let x = Math.cos(angle) * dx - Math.sin(angle) * dy + width / 2;
    let y = Math.sin(angle) * dx + Math.cos(angle) * dy + height / 2;
    if (object?.flipX) x = width - x;
    if (object?.flipY) y = height - y;
    return { x, y };
  }

  function localPoint(object, point) {
    const local = localPointUnclamped(object, point); const width = Math.max(1, finite(object?.width, 1)); const height = Math.max(1, finite(object?.height, 1));
    return local.x >= 0 && local.x <= width && local.y >= 0 && local.y <= height ? local : null;
  }

  function pixelPoint(object, point, dimensions) {
    const local = localPoint(object, point); if (!local) return null;
    const width = Math.max(1, Math.round(finite(dimensions?.width, 1))); const height = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    return {
      x: clamp(Math.floor(local.x * width / Math.max(1, finite(object?.width, 1))), 0, width - 1),
      y: clamp(Math.floor(local.y * height / Math.max(1, finite(object?.height, 1))), 0, height - 1)
    };
  }

  function pixelPointUnclamped(object, point, dimensions) {
    const local = localPointUnclamped(object, point);
    const width = Math.max(1, Math.round(finite(dimensions?.width, 1))); const height = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    return {
      x: Math.floor(local.x * width / Math.max(1, finite(object?.width, 1))),
      y: Math.floor(local.y * height / Math.max(1, finite(object?.height, 1)))
    };
  }

  function clampPixelPoint(point, dimensions) {
    const width = Math.max(1, Math.round(finite(dimensions?.width, 1))); const height = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    return { x: clamp(Math.floor(finite(point?.x)), 0, width - 1), y: clamp(Math.floor(finite(point?.y)), 0, height - 1) };
  }

  function pixelDimensions(object) {
    return {
      width: Math.max(1, Math.round(finite(object?.pixelWidth, finite(object?.width, 1)))),
      height: Math.max(1, Math.round(finite(object?.pixelHeight, finite(object?.height, 1))))
    };
  }

  function toleranceFromSlider(value) {
    const position = clamp(Math.round(finite(value, 96)), 0, 200);
    return position <= 150 ? Math.round(position / 3) : 50 + position - 150;
  }

  function sliderPositionForTolerance(value) {
    const tolerance = clamp(Math.round(finite(value, 32)), 0, 100);
    return tolerance <= 50 ? tolerance * 3 : 150 + tolerance - 50;
  }

  function marqueeRect(start, current, dimensions, settings = {}) {
    const limits = { width: Math.max(1, finite(dimensions?.width, Infinity)), height: Math.max(1, finite(dimensions?.height, Infinity)) };
    if (settings.style === 'size' && Number.isFinite(limits.width) && Number.isFinite(limits.height)) {
      const width = Math.min(Math.max(1, finite(settings.width, 1)), limits.width);
      const height = Math.min(Math.max(1, finite(settings.height, 1)), limits.height);
      if (settings.clip === false) return { x: start.x, y: start.y, width, height };
      return { x: clamp(start.x, 0, limits.width - width), y: clamp(start.y, 0, limits.height - height), width, height };
    }
    if (settings.style === 'aspect') {
      const aspect = clamp(finite(settings.aspect, 1), .01, 100);
      const dx = current.x - start.x; const dy = current.y - start.y; const rawWidth = Math.abs(dx) + 1; const rawHeight = Math.abs(dy) + 1;
      let width; let height;
      if (rawWidth / rawHeight >= aspect) { width = rawWidth; height = Math.max(1, Math.round(width / aspect)); }
      else { height = rawHeight; width = Math.max(1, Math.round(height * aspect)); }
      let x = dx < 0 ? start.x - width + 1 : start.x; let y = dy < 0 ? start.y - height + 1 : start.y;
      if (settings.clip !== false && Number.isFinite(limits.width)) { x = clamp(x, 0, limits.width - 1); width = Math.min(width, limits.width - x); }
      if (settings.clip !== false && Number.isFinite(limits.height)) { y = clamp(y, 0, limits.height - 1); height = Math.min(height, limits.height - y); }
      return { x, y, width, height };
    }
    const x = Math.min(start.x, current.x); const y = Math.min(start.y, current.y);
    return { x, y, width: Math.abs(current.x - start.x) + 1, height: Math.abs(current.y - start.y) + 1 };
  }

  function marqueeIntersection(start, current, dimensions, settings = {}) {
    const width = Math.max(1, Math.round(finite(dimensions?.width, 1))); const height = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    const raw = marqueeRect(start, current, { width, height }, { ...settings, clip: false });
    const left = Math.max(0, raw.x); const top = Math.max(0, raw.y);
    const right = Math.min(width, raw.x + raw.width); const bottom = Math.min(height, raw.y + raw.height);
    if (right <= left || bottom <= top) return null;
    return { x: left, y: top, width: right - left, height: bottom - top };
  }

  function ellipseSelectionFromRect(rect, dimensions) {
    const sourceWidth = Math.max(1, Math.round(finite(dimensions?.width, 1))); const sourceHeight = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    const ellipse = {
      x: Math.round(finite(rect?.x)), y: Math.round(finite(rect?.y)),
      width: Math.max(1, Math.round(finite(rect?.width, 1))), height: Math.max(1, Math.round(finite(rect?.height, 1)))
    };
    const left = Math.max(0, ellipse.x); const top = Math.max(0, ellipse.y);
    const right = Math.min(sourceWidth, ellipse.x + ellipse.width); const bottom = Math.min(sourceHeight, ellipse.y + ellipse.height);
    if (right <= left || bottom <= top) return null;
    const width = right - left; const height = bottom - top; const mask = new Uint8Array(width * height);
    const radiusX = ellipse.width / 2; const radiusY = ellipse.height / 2;
    const centerX = ellipse.x + radiusX; const centerY = ellipse.y + radiusY; let count = 0;
    for (let y = 0; y < height; y += 1) {
      const normalizedY = (top + y + .5 - centerY) / radiusY;
      for (let x = 0; x < width; x += 1) {
        const normalizedX = (left + x + .5 - centerX) / radiusX;
        if (normalizedX * normalizedX + normalizedY * normalizedY > 1) continue;
        mask[y * width + x] = 1; count += 1;
      }
    }
    return count ? { x: left, y: top, width, height, ellipse, mask, count } : null;
  }

  function ellipseSelection(start, current, dimensions, settings = {}) {
    const raw = marqueeRect(start, current, dimensions, { ...settings, clip: false });
    return ellipseSelectionFromRect(raw, dimensions);
  }

  function lassoBounds(points, dimensions) {
    const width = Math.max(1, Math.round(finite(dimensions?.width, 1))); const height = Math.max(1, Math.round(finite(dimensions?.height, 1)));
    const valid = (points || []).filter((point) => Number.isFinite(Number(point?.x)) && Number.isFinite(Number(point?.y)));
    if (!valid.length) return { x: 0, y: 0, width: 1, height: 1 };
    const minX = clamp(Math.floor(Math.min(...valid.map((point) => point.x))), 0, width - 1);
    const maxX = clamp(Math.ceil(Math.max(...valid.map((point) => point.x))), 0, width - 1);
    const minY = clamp(Math.floor(Math.min(...valid.map((point) => point.y))), 0, height - 1);
    const maxY = clamp(Math.ceil(Math.max(...valid.map((point) => point.y))), 0, height - 1);
    return { x: minX, y: minY, width: Math.max(1, maxX - minX + 1), height: Math.max(1, maxY - minY + 1) };
  }

  function pointInPolygon(x, y, points) {
    let inside = false;
    for (let index = 0, previous = points.length - 1; index < points.length; previous = index, index += 1) {
      const a = points[index]; const b = points[previous];
      const intersects = (a.y > y) !== (b.y > y) && x < (b.x - a.x) * (y - a.y) / ((b.y - a.y) || .000001) + a.x;
      if (intersects) inside = !inside;
    }
    return inside;
  }

  function lassoSelection(points, dimensions) {
    const contour = (points || []).map((point) => ({ x: Math.round(finite(point?.x)), y: Math.round(finite(point?.y)) }));
    if (contour.length < 3) return null;
    const bounds = lassoBounds(contour, dimensions); const mask = new Uint8Array(bounds.width * bounds.height); let count = 0;
    for (let y = 0; y < bounds.height; y += 1) {
      for (let x = 0; x < bounds.width; x += 1) {
        if (!pointInPolygon(bounds.x + x + .5, bounds.y + y + .5, contour)) continue;
        mask[y * bounds.width + x] = 1; count += 1;
      }
    }
    return count ? { ...bounds, points: contour, mask, count } : null;
  }

  function wandSelection(pixels, widthValue, heightValue, seed, toleranceValue = 0) {
    const width = Math.max(1, Math.round(finite(widthValue, 1))); const height = Math.max(1, Math.round(finite(heightValue, 1)));
    if (!pixels || pixels.length < width * height * 4) throw new Error('Bitmapdaten sind unvollständig');
    const tolerance = clamp(Math.round(finite(toleranceValue)), 0, 100);
    const seedX = clamp(Math.round(finite(seed?.x)), 0, width - 1); const seedY = clamp(Math.round(finite(seed?.y)), 0, height - 1);
    const pixelCount = width * height; const start = seedY * width + seedX; const startOffset = start * 4;
    const target = [pixels[startOffset], pixels[startOffset + 1], pixels[startOffset + 2], pixels[startOffset + 3]];
    const matches = (index) => {
      const offset = index * 4;
      return Math.max(
        Math.abs(pixels[offset] - target[0]), Math.abs(pixels[offset + 1] - target[1]),
        Math.abs(pixels[offset + 2] - target[2]), Math.abs(pixels[offset + 3] - target[3])
      ) <= tolerance;
    };
    const mask = new Uint8Array(pixelCount); const queue = new Int32Array(pixelCount); let head = 0; let tail = 1;
    mask[start] = 1; queue[0] = start;
    let minX = seedX; let maxX = seedX; let minY = seedY; let maxY = seedY;
    const enqueue = (index) => {
      if (index < 0 || index >= pixelCount || mask[index] || !matches(index)) return;
      mask[index] = 1; queue[tail++] = index;
    };
    while (head < tail) {
      const index = queue[head++]; const x = index % width; const y = Math.floor(index / width);
      minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y);
      if (x > 0) enqueue(index - 1); if (x + 1 < width) enqueue(index + 1);
      if (y > 0) enqueue(index - width); if (y + 1 < height) enqueue(index + width);
    }
    return {
      x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1,
      sourceWidth: width, sourceHeight: height, mask, count: tail, tolerance
    };
  }

  const api = Object.freeze({
    localPoint, localPointUnclamped, pixelPoint, pixelPointUnclamped, clampPixelPoint, pixelDimensions, toleranceFromSlider, sliderPositionForTolerance,
    marqueeRect, marqueeIntersection, ellipseSelectionFromRect, ellipseSelection, lassoBounds, pointInPolygon, lassoSelection, wandSelection
  });
  root.PyroBitmapSelection = api;
  if (typeof module === 'object' && module?.exports) module.exports = api;
})(typeof globalThis !== 'undefined' ? globalThis : self);
