Skip to content

WIP - Add circle crop mask support#79393

Draft
ramonjd wants to merge 4 commits into
trunkfrom
ramonjd/circle-crop-stencil
Draft

WIP - Add circle crop mask support#79393
ramonjd wants to merge 4 commits into
trunkfrom
ramonjd/circle-crop-stencil

Conversation

@ramonjd

@ramonjd ramonjd commented Jun 22, 2026

Copy link
Copy Markdown
Member

What?

Adds MVP support for circular crops in the media editor. At the moment its state is MVP/POC to guide direction.

Kapture.2026-06-26.at.14.26.45.mp4

Backport PR here: WordPress/wordpress-develop#12327

  • Adds circle crop mask handling to the attachments REST edit flow.
  • Adds Gutenberg image editor subclasses for applying image masks.
  • Adds Rectangle/Circle stencil selection in the crop panel.
  • Adds a mobile/footer toolbar crop-shape dropdown.
  • Uses edge handles for the circle stencil so resizing feels like dragging the circle edge.
  • Saves circle crops as PNG to preserve transparency.

Testing Instructions

  1. Open the media editor for an image.
  2. Go to the Crop panel.
  3. Confirm the Shape control shows Rectangle and Circle.
  4. Select Rectangle and confirm aspect-ratio controls are available.
  5. Select Circle and confirm aspect-ratio controls are hidden.
  6. Crop and save the image.
  7. Confirm the saved result is circular with transparent corners and is saved as PNG.
  8. On a narrow/mobile viewport, confirm the footer toolbar shows the crop-shape dropdown.
  9. Use the mobile dropdown to switch between Rectangle and Circle.
  10. Confirm circle resizing uses the middle edge handles instead of corner handles.

@ramonjd ramonjd changed the title Add circle crop mask support WIP - Add circle crop mask support Jun 22, 2026
@ramonjd ramonjd self-assigned this Jun 22, 2026
@ramonjd ramonjd added [Type] Experimental Experimental feature or API. No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core labels Jun 22, 2026
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Size Change: +960 B (+0.01%)

Total Size: 7.5 MB

📦 View Changed
Filename Size Change
build/scripts/editor/index.min.js 477 kB +830 B (+0.17%)
build/styles/editor/style-rtl.css 30.8 kB +28 B (+0.09%)
build/styles/editor/style-rtl.min.css 26.2 kB +35 B (+0.13%)
build/styles/editor/style.css 30.9 kB +32 B (+0.1%)
build/styles/editor/style.min.css 26.2 kB +35 B (+0.13%)

compressed-size-action

*
* @return array
*/
protected function get_edit_media_item_args() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is vibed coded as I wanted to test the flow first before doing it the right way. If it's a good direction, we'll do it with a more staggered approach.

@ramonjd ramonjd force-pushed the ramonjd/circle-crop-stencil branch from 3486e5e to a63aeb2 Compare June 22, 2026 07:29
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
*/
public function edit_media_item( $request ) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

non-mask request
→ parent::edit_media_item( original request )
→ done

circle mask request
→ strip mask modifier
→ parent::edit_media_item( request without mask )
→ Core creates unmasked edited attachment files
→ Gutenberg creates masked PNG replacement
→ delete Core-created unmasked files for that new attachment
→ regenerate metadata for PNG

@ramonjd ramonjd force-pushed the ramonjd/circle-crop-stencil branch 2 times, most recently from 509d976 to 236386f Compare June 23, 2026 06:46
* @param WP_REST_Request $request Full details about the request.
* @return bool Whether the request has a circle mask modifier.
*/
protected function gutenberg_has_circle_mask_modifier( $request ) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Maybe could be done inline too. Pretty chunky in there though.

@github-actions

Copy link
Copy Markdown

Flaky tests detected in 236386f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28007715228
📝 Reported issues:

@ramonjd ramonjd force-pushed the ramonjd/circle-crop-stencil branch from 236386f to 21c22f7 Compare June 26, 2026 03:46
@ramonjd ramonjd force-pushed the ramonjd/circle-crop-stencil branch from 21c22f7 to 792717d Compare June 26, 2026 03:54
* @param array $args Test arguments.
* @return bool Whether this editor is supported.
*/
public static function test( $args = array() ) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

/**
* Minimal disposable helper for mask argument validation.
*/
class Gutenberg_Image_Editor_Mask {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just to confirm, this is a GB only helper. Maybe it could be a function, and named _gutenberg_something... and given a @private annotation?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds MVP/POC support for circular image crops in the Media Editor, spanning UI/state updates in packages/media-editor and a WordPress REST /edit compatibility layer that can apply a circular mask and output PNG to preserve transparency.

Changes:

  • Introduces a cropShape option (rectangle/circle) with undo/history integration and “dirty” tracking.
  • Adds a circular stencil UI (edge handles + circular overlay styling) and exposes shape selection in the Crop panel and mobile/toolbar controls.
  • Extends the attachments /edit flow with an experimental mask modifier and registers mask-capable image editors (Imagick/GD) to produce circular PNG outputs.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/media-editor/src/state/use-media-editor-state.ts Adds setCropShape controller API and includes crop shape in dirty tracking.
packages/media-editor/src/state/types.ts Defines CropShape and adds cropShape to CropOptionsSlice + action types.
packages/media-editor/src/state/test/use-media-editor-state.ts Adds coverage for undo/dirty behavior when switching crop shape.
packages/media-editor/src/state/index.ts Re-exports CropShape type.
packages/media-editor/src/state/composite-reducer.ts Implements SET_CROP_SHAPE handling and includes crop shape in equality checks.
packages/media-editor/src/image-editor/react/components/stencils/test/circle-stencil.tsx Adds unit test ensuring circle stencil uses edge handles (not corners).
packages/media-editor/src/image-editor/react/components/stencils/rectangle-stencil.tsx Adds internal props to support circle styling and edge-handle locking.
packages/media-editor/src/image-editor/react/components/stencils/circle-stencil.tsx Implements circle stencil by composing RectangleStencil with square constraints.
packages/media-editor/src/image-editor/react/components/index.ts Exports CircleStencil.
packages/media-editor/src/image-editor/react/components/cropper.tsx Adds stencilShape support and enforces square constraints for circle crops.
packages/media-editor/src/image-editor/react/components/cropper.scss Adds circular overlay styling (grid/dimming/stencil border radius).
packages/media-editor/src/image-editor/index.ts Re-exports CircleStencil from the package surface.
packages/media-editor/src/image-editor/core/test/stencil-math.ts Adds test coverage for locked edge-handle resizing behavior.
packages/media-editor/src/image-editor/core/stencil-math.ts Adds computeLockedEdgeResizeRect and routes edge handles through it.
packages/media-editor/src/components/media-editor/use-save-media-editor.ts Appends mask modifier when cropShape is circle.
packages/media-editor/src/components/media-editor/use-crop-options.ts Exposes cropShape setters and resolves circle to a square aspect ratio.
packages/media-editor/src/components/media-editor/test/use-crop-options.tsx Adds coverage for circle resolved aspect ratio and reset behavior.
packages/media-editor/src/components/media-editor/style.scss Tweaks sidebar padding on small breakpoints.
packages/media-editor/src/components/media-editor/index.tsx Wires crop shape into Crop panel and hides aspect ratio controls for circles.
packages/media-editor/src/components/media-editor-modal/build-modifiers.ts Extends modifier union type to include mask.
packages/media-editor/src/components/media-editor-image-controls/test/index.tsx Adds test coverage for toolbar crop-shape dropdown and circle behavior.
packages/media-editor/src/components/media-editor-image-controls/style.scss Adjusts toolbar layout wrapping/centering for added controls.
packages/media-editor/src/components/media-editor-image-controls/index.tsx Adds crop-shape dropdown and hides aspect ratio dropdown for circle crops.
packages/media-editor/src/components/media-editor-crop-panel/test/index.tsx Adds test coverage for crop shape selection and circle-specific UI behavior.
packages/media-editor/src/components/media-editor-crop-panel/index.tsx Adds shape toggle group and conditionally renders aspect ratio selector.
packages/media-editor/src/components/media-editor-canvas/index.tsx Passes circle stencil + stencilShape to the Cropper when shape is circle.
lib/media/class-gutenberg-rest-attachments-controller.php Switches controller base to a mask-capable attachments controller.
lib/compat/wordpress-7.1/image-editor-mask.php Registers mask-capable Gutenberg image editors via wp_image_editors filter.
lib/compat/wordpress-7.1/image-editor-mask-validation.php Adds validation/normalization helper for mask arguments.
lib/compat/wordpress-7.1/class-gutenberg-rest-attachments-controller-with-mask.php Adds mask modifier schema and custom /edit flow to apply mask + PNG output.
lib/compat/wordpress-7.1/class-gutenberg-rest-attachments-controller-7-1.php Updates 7.1 controller to inherit mask-capable base.
lib/compat/wordpress-7.1/class-gutenberg-image-editor-imagick.php Adds Imagick editor subclass implementing mask() for circle cropping.
lib/compat/wordpress-7.1/class-gutenberg-image-editor-gd.php Adds GD editor subclass implementing mask() for circle cropping.

Comment on lines +44 to +45
'title' => __( 'Mask', 'gutenberg' ),
'properties' => array(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Type] Experimental Experimental feature or API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants