Skip to content

feat(editor): pass through clicks on transparent image pixels#7942

Merged
steveruizok merged 7 commits intomainfrom
steve/transparent-png-click-through
Feb 24, 2026
Merged

feat(editor): pass through clicks on transparent image pixels#7942
steveruizok merged 7 commits intomainfrom
steve/transparent-png-click-through

Conversation

@steveruizok
Copy link
Copy Markdown
Collaborator

@steveruizok steveruizok commented Feb 15, 2026

Closes #7529

In order to allow clicks on transparent parts of PNG/WebP/GIF/AVIF images to pass through to shapes behind them, this PR adds an ignoreHit() mechanism to the geometry system and uses it to check image alpha data at the click point.

pr-7942-walkthrough.mp4

How it works

  1. Geometry2d.ignoreHit(point) — New method on the base geometry class (default: false). Called after a standard hit test succeeds. If it returns true, the shape is skipped and the click passes through to shapes behind it. Also wired through TransformedGeometry2d via inverse matrix.

  2. ImageAlphaCache — Preloads and caches a downsampled (max 256×256) alpha channel for each image. Uses Uint32Array with unsigned right shift for efficient alpha byte extraction. The preload URL and cache key are separated so resolved/CDN URLs work correctly with asset.props.src lookups. A shared OffscreenCanvas is reused across preloads, with canvas operations kept synchronous after the async createImageBitmap to prevent concurrent corruption.

  3. ImageAlphaGeometry.ts — Contains ImageRectangle2d (extends Rectangle2d) and ImageEllipse2d (extends Ellipse2d), both with ignoreHit and hitTestPoint overrides that map shape-space points to normalized image coordinates (accounting for crop, flipX, flipY) and check the alpha cache. Coordinates are clamped to [0,1] so edge-margin hits slightly outside bounds map to the nearest edge pixel.

  4. ImageShapeUtilgetGeometry() now returns ImageRectangle2d for transparent image formats, or ImageEllipse2d when crop.isCircle is set. The ImageShape component preloads alpha data when the resolved URL is available, using asset.props.src as the cache key.

  5. Editor.getShapeAtPoint — After a filled shape geometry hit succeeds, calls geometry.ignoreHit(pointInShapeSpace). If true, continues to check shapes behind it.

The hitTestPoint override on both geometry classes ensures that selection paths using isPointInShape / getSelectedShapeAtPoint also skip transparent pixels. Before alpha data is loaded, images behave as fully opaque (no change from current behavior).

Change type

  • feature

Test plan

  1. yarn dev → import a PNG with transparency over another shape
  2. Click on a transparent area of the PNG → the shape behind should be selected
  3. Click on an opaque area of the PNG → the image should be selected
  4. Test with cropped, flipped, and circle-cropped images
  5. Test edge clicks near the boundary of transparent images
  • Unit tests

Release notes

  • Add click-through on transparent pixels of PNG, WebP, GIF, and AVIF images. Clicking transparent areas now selects shapes behind the image instead of the image itself. Works with cropped, flipped, and circle-cropped images.

API changes

  • Added Geometry2d.ignoreHit(point) — allows geometries to reject successful hit tests (e.g. for transparent pixels)

Add a rejectHit() method to Geometry2d that allows shapes to reject
hit test results after the standard geometry check passes. Use this
in a new ImageRectangle2d geometry that checks a cached alpha map to
detect transparent pixels in PNG, WebP, and GIF images. When a click
lands on a transparent pixel, the shape is skipped and the click
passes through to shapes behind it.

Closes #7529
@huppy-bot huppy-bot bot added the feature New feature label Feb 15, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 23, 2026 11:48pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 23, 2026 11:48pm
chat-template Ignored Ignored Preview Feb 23, 2026 11:48pm
tldraw-docs Ignored Ignored Preview Feb 23, 2026 11:48pm
tldraw-shader Ignored Ignored Preview Feb 23, 2026 11:48pm
workflow-template Ignored Ignored Preview Feb 23, 2026 11:48pm

Request Review

Override hitTestPoint on ImageRectangle2d so that selection paths
using isPointInShape / getSelectedShapeAtPoint also skip transparent
pixels. Extract shared mapToImageCoords helper with [0,1] clamping
to handle edge-margin hits slightly outside shape bounds.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
When crop.isCircle was set, getGeometry() returned a plain Ellipse2d
that bypassed transparency hit logic. Add ImageEllipse2d (mirrors
ImageRectangle2d) with hitTestPoint/rejectHit alpha checking, and
use it for circle-cropped transparent images.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown
Contributor

@kaneel kaneel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't mind the tiny "what ifs" on speed.
But maybe we can improve the way we check mimetypes and actually have a better map of all possible mimetypes.

…ough

- Rename rejectHit to ignoreHit on Geometry2d base class (fixes naming
  mismatch where Editor.ts already used ignoreHit)
- Extract shared alpha hit-testing logic from ImageRectangle2d and
  ImageEllipse2d into ImageAlphaCache helpers to reduce duplication
- Replace inline mimeType string checks with TRANSPARENT_IMAGE_MIMETYPES
  constant array

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ha extraction

Use createImageBitmap with resize options to move the heavy image
resize off the main thread during alpha data preloading. Reuse a
single OffscreenCanvas instance across preloads instead of creating
a new canvas element each time. Falls back to sync resize for
browsers without createImageBitmap resize support.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ough

- Use TRANSPARENT_IMAGE_MIMETYPES constant consistently (no more substring matching)
- Merge ImageRectangle2d and ImageEllipse2d into single file (ImageAlphaGeometry.ts)
- Use Uint32Array >>> 24 trick for faster alpha byte extraction
- Fix concurrent OffscreenCanvas corruption by moving canvas ops after async bitmap creation
- Add image/avif to transparent mimetypes

Co-Authored-By: Claude Opus 4.6 <[email protected]>
preloadAlphaData was keyed by the resolved URL (which may be a CDN/optimized
URL), but getGeometry looked up alpha data by asset.props.src. When these
differed, the cache always missed and click-through never activated.

Now preloadAlphaData accepts a separate cacheKey param. The component passes
asset.props.src as the key, ensuring getGeometry finds the preloaded data.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

@steveruizok steveruizok changed the title feat(editor): pass through clicks on transparent PNG pixels feat(editor): pass through clicks on transparent image pixels Feb 23, 2026
@steveruizok steveruizok added this pull request to the merge queue Feb 24, 2026
Merged via the queue into main with commit 82c41f8 Feb 24, 2026
30 checks passed
@steveruizok steveruizok deleted the steve/transparent-png-click-through branch February 24, 2026 00:16
steveruizok added a commit that referenced this pull request Feb 27, 2026
Add 12 new entries from PRs merged since v4.4.0:
- Featured: click-through on transparent image pixels (#7942)
- API: enum-to-const-object refactor (#8084)
- Improvements: SVG sanitizer (#7896), save-on-blur (#8037)
- Bug fixes: cross-origin download (#8090), zero-size draw (#8067),
  rich text toolbar cleanup (#8050), zoom threshold (#8040),
  selection foreground fallback (#8011), sticky note SVG shadow (#7934),
  arrow frame clamping (#7932), zero pressure draw (#5693)
github-merge-queue bot pushed a commit that referenced this pull request Mar 4, 2026
Update `next.mdx` release notes to cover all SDK-relevant PRs merged to
main since v4.4.0.

Highlights:
- Display values system (#8121) with breaking changes and migration
guide
- Click-through on transparent image pixels (#7942)
- `Editor.resizeToBounds()` (#8120)
- SVG sanitization (#7896)
- TypeScript enum-to-const refactoring (#8084)
- 14 bug fixes and 4 improvements

### Change type

- [x] `other`

### Test plan

1. Verify the release notes render correctly on the docs site

### Release notes

- Update next release notes with changes since v4.4.0.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk: documentation-only changes updating release notes content
and date, with no runtime/code behavior impact.
> 
> **Overview**
> Updates `apps/docs/content/releases/next.mdx` for the upcoming release
by refreshing the date and replacing the brief blurb with expanded
release notes.
> 
> Documents new SDK surface area (`Geometry2d.ignoreHit`,
`Editor.resizeToBounds`, `sanitizeSvg`), highlights click-through on
transparent image pixels, and adds a list of recent improvements and bug
fixes (paste parenting, link/alt-text persistence, SVG sanitization
behavior, circular-dependency cleanup, and several crash/export/sync
fixes).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5f7dc0a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
github-merge-queue bot pushed a commit that referenced this pull request Mar 18, 2026
In order to publish the v4.5.0 release notes and record the v4.4.1
patch, this PR archives release notes and resets `next.mdx` for the next
cycle.

**v4.5.0.mdx** (new file):
- Archived from `next.mdx` with full frontmatter, keywords, and GitHub
release link
- Featured sections: click-through on transparent image pixels (#7942),
breaking `EmbedShapeUtil.configure()` change (#8034)
- API changes: `Editor.getResizeScaleFactor()` (#8042),
`TLImageAsset.pixelRatio` (#8163), `sanitizeSvg` (#7896),
`experimental__onDropOnCanvas` (#7911), enum-to-const refactoring
(#8084)
- 6 improvements and 20 bug fixes from production

**v4.4.0.mdx:**
- Add v4.4.1 patch release section with tooltip positioning fix (#8171)
- Add v4.4.1 to keywords

**next.mdx:**
- Reset with `last_version: v4.5.0` and empty content

### Change type

- [x] `other`

### Code changes

| Section       | LOC change    |
| ------------- | ------------- |
| Documentation | +128 / -107   |
huppy-bot bot pushed a commit that referenced this pull request Mar 18, 2026
In order to publish the v4.5.0 release notes and record the v4.4.1
patch, this PR archives release notes and resets `next.mdx` for the next
cycle.

**v4.5.0.mdx** (new file):
- Archived from `next.mdx` with full frontmatter, keywords, and GitHub
release link
- Featured sections: click-through on transparent image pixels (#7942),
breaking `EmbedShapeUtil.configure()` change (#8034)
- API changes: `Editor.getResizeScaleFactor()` (#8042),
`TLImageAsset.pixelRatio` (#8163), `sanitizeSvg` (#7896),
`experimental__onDropOnCanvas` (#7911), enum-to-const refactoring
(#8084)
- 6 improvements and 20 bug fixes from production

**v4.4.0.mdx:**
- Add v4.4.1 patch release section with tooltip positioning fix (#8171)
- Add v4.4.1 to keywords

**next.mdx:**
- Reset with `last_version: v4.5.0` and empty content

### Change type

- [x] `other`

### Code changes

| Section       | LOC change    |
| ------------- | ------------- |
| Documentation | +128 / -107   |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pass through click events on transparent parts of PNGs

3 participants