skipObjects.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536
  1. export function pickObjectIdAt(imageData: ImageData, x: number, y: number): number | null {
  2. const pixelX = Math.max(0, Math.min(imageData.width - 1, Math.floor(x)));
  3. const pixelY = Math.max(0, Math.min(imageData.height - 1, Math.floor(y)));
  4. const offset = (pixelY * imageData.width + pixelX) * 4;
  5. const red = imageData.data[offset];
  6. const green = imageData.data[offset + 1];
  7. const blue = imageData.data[offset + 2];
  8. const alpha = imageData.data[offset + 3];
  9. if (!alpha || (red === 0 && green === 0 && blue === 0)) return null;
  10. return red + (green << 8) + (blue << 16);
  11. }
  12. /**
  13. * Map a click on the plate canvas to a pixel in the object-ID mask.
  14. *
  15. * The canvas paints at the mask's own resolution and is displayed with
  16. * object-contain inside a square box, so a mask that isn't square is
  17. * letterboxed. Measuring off the element rect would then offset every click by
  18. * the size of the bars. Returns null for clicks on a bar: those are off the
  19. * plate, and pickObjectIdAt would otherwise clamp them onto whichever object
  20. * touches that border.
  21. */
  22. export function plateClickToMaskPoint(
  23. bounds: { left: number; top: number; width: number; height: number },
  24. maskWidth: number,
  25. maskHeight: number,
  26. clientX: number,
  27. clientY: number,
  28. ): { x: number; y: number } | null {
  29. const scale = Math.min(bounds.width / maskWidth, bounds.height / maskHeight);
  30. if (!(scale > 0)) return null;
  31. const x = (clientX - bounds.left - (bounds.width - maskWidth * scale) / 2) / scale;
  32. const y = (clientY - bounds.top - (bounds.height - maskHeight * scale) / 2) / scale;
  33. if (x < 0 || y < 0 || x >= maskWidth || y >= maskHeight) return null;
  34. return { x, y };
  35. }