stitchpane-core 0.1.0

Spec-Driven Image Editor In Rust
Feature: Composition

  Background:
    Given a "red" image "a.png" of 100 by 50 pixels
    And a "blue" image "b.png" of 40 by 80 pixels

  @CANVAS-001
  Scenario Outline: Canvas size choices
    When I create a composition of "a.png" and "b.png" with canvas "<choice>"
    Then the canvas is <width> by <height> pixels
    And image 1 is at <x1>, <y1>
    And image 2 is at <x2>, <y2>

    Examples:
      | choice                    | width | height | x1  | y1  | x2   | y2    |
      | Side by side              | 140   | 80     | 0   | 15  | 100  | 0     |
      | Stacked                   | 100   | 130    | 0   | 0   | 30   | 50    |
      | Size of the largest image | 100   | 50     | 0   | 0   | 30   | -15   |
      | 1920 × 1080               | 1920  | 1080   | 910 | 515 | 940  | 500   |
      | 3840 × 2160               | 3840  | 2160   | 1870| 1055| 1900 | 1040  |

  @CANVAS-001
  Scenario: Paper presets set the DPI
    When I create a composition of "a.png" and "b.png" with canvas "A4 at 300 DPI"
    Then the canvas is 2480 by 3508 pixels
    And the canvas DPI is 300
    When I create a composition of "a.png" and "b.png" with canvas "US Letter at 300 DPI"
    Then the canvas is 2550 by 3300 pixels

  @CANVAS-001
  Scenario: Custom canvas size
    When I create a composition of "a.png" and "b.png" with a custom canvas of 300 by 200 pixels at 72 DPI
    Then the canvas is 300 by 200 pixels
    And the canvas DPI is 72

  @CANVAS-001
  Scenario: Opening several images defaults to side by side
    When I open "a.png" and "b.png" together
    Then the canvas is 140 by 80 pixels

  @CANVAS-002
  Scenario: Resize the canvas by numbers
    Given a composition of "a.png" and "b.png" side by side
    When I set the canvas size to 300 by 200 pixels at 96 DPI
    Then the canvas is 300 by 200 pixels
    And the canvas DPI is 96
    And the undo label is "Undo Resize canvas"

  @CANVAS-003
  Scenario: Resize the canvas with a handle
    Given a composition of "a.png" and "b.png" side by side
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I drag from 140, 40 to 160, 40
    Then the canvas is 160 by 80 pixels

  @CANVAS-003
  Scenario: Growing the canvas to the left keeps images in place on screen
    Given a composition of "a.png" and "b.png" side by side
    And the view is at 100% with the canvas origin on screen at 50, 50
    And I remember where image 1 is on screen
    When I drag from 0, 40 to -20, 40
    Then the canvas is 160 by 80 pixels
    And image 1 is at 20, 15
    And image 1 is where I remembered on screen

  @CANVAS-004
  Scenario: Uncovered canvas is transparent
    Given a composition of "a.png" and "b.png" side by side
    Then the rendered canvas at 50, 5 is "transparent"

  @PLACE-001
  Scenario: Add images to a composition
    Given a composition of "a.png" and "b.png" with canvas "Size of the largest image"
    And a "green" image "c.png" of 20 by 20 pixels
    When I add "c.png"
    Then the project has 3 images
    And image 3 is at 40, 15
    And image 3 is on top

  @PLACE-001
  Scenario: Drop images onto the canvas
    Given a composition of "a.png" and "b.png" side by side
    And the view is at 100% with the canvas origin on screen at 50, 50
    And a "green" image "c.png" of 20 by 20 pixels
    When I drop "c.png" on the canvas at 70, 30
    Then image 3 is at 60, 20
    And image 3 is on top

  @PLACE-001 @UI-005
  Scenario: Add an image to a single image
    Given I open "a.png"
    When I add "b.png"
    Then the project has 2 images
    And image 2 is at 100, 0
    And the canvas is 140 by 80 pixels

  @PLACE-002
  Scenario: Drag to move
    Given a composition of "a.png" and "b.png" side by side
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I drag from 50, 25 to 61, 32
    Then image 1 is at 11, 22
    And the undo label is "Undo Move image"

  @PLACE-003
  Scenario: Corner handles keep the aspect ratio
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I select image 1
    And I drag from 250, 225 to 350, 235
    Then image 1 is 200 by 100 pixels
    And image 1 is at 150, 175

  @PLACE-003
  Scenario: Shift frees the aspect ratio
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I select image 1
    And I drag from 250, 225 to 260, 245 with Shift
    Then image 1 is 110 by 70 pixels

  @PLACE-003
  Scenario: Edge handles change one dimension and never the source
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I select image 1
    And I drag from 250, 200 to 270, 200
    Then image 1 is 120 by 50 pixels
    And the undo label is "Undo Resize image"
    And "a.png" is unchanged on disk

  @PLACE-004
  Scenario: Crop an item in a composition
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    When I select image 1
    And I start cropping the image
    And I set the crop rectangle to 0, 0, 50, 50
    And I press "Enter"
    Then image 1 is 50 by 50 pixels
    And image 1 is at 150, 175
    And the canvas is 400 by 400 pixels

  @CROP-005
  Scenario: Uncrop an item cropped several steps earlier
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I select image 1
    And I start cropping the image
    And I set the crop rectangle to 0, 0, 50, 50
    And I press "Enter"
    And image 2 is moved to 10, 10
    And I select image 1
    And I start cropping the image
    Then image 1 is shown whole and on top while cropping
    And the crop rectangle is 0, 0, 50, 50
    When I drag from 200, 200 to 250, 200
    Then the crop rectangle is 0, 0, 100, 50
    When I press "Enter"
    Then image 1 is 100 by 50 pixels
    And image 1 is at 150, 175
    And image 2 is on top

  @PLACE-009
  Scenario: Remove an image from its context menu
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And a crossfade between image 1 and image 2
    When I open the context menu on image 2
    And I choose "Remove image"
    Then the project has 1 images
    And there are 0 transitions
    And the undo label is "Undo Remove image"
    When I undo
    Then the project has 2 images
    And there is 1 transition

  @PLACE-009
  Scenario: Delete removes the selected image
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    When I select image 1
    And I press "Delete"
    Then the project has 1 images
    When I select image 2
    And I press "Backspace"
    Then the project has 0 images

  @PLACE-005
  Scenario: Change the stacking order
    Given a composition of "a.png" and "b.png" with canvas "Size of the largest image"
    When I open the context menu on image 1
    Then the menu has "Bring to front", "Bring forward", "Send backward" and "Send to back"
    When I choose "Bring to front"
    Then image 1 is on top
    When I open the context menu on image 1
    And I choose "Send backward"
    Then image 2 is on top

  @PLACE-006
  Scenario: Dragging snaps to other images and shows a guide
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I select image 2
    And I move the pointer from 200, 200 to 228, 200 without releasing
    Then a vertical guide is shown at 250
    When I release the pointer at 228, 200
    Then image 2 is at 210, 160

  @PLACE-006
  Scenario: Alt disables snapping for one drag
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I select image 2
    And I drag from 200, 200 to 228, 200 holding Alt
    Then image 2 is at 208, 160

  @PLACE-006
  Scenario: Snapping can be turned off
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I select image 2
    And I drag from 200, 200 to 228, 200
    Then image 2 is at 208, 160

  @PLACE-006 @UI-006
  Scenario: Keyboard adjustments never snap
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    When I select image 2
    And I press "Right"
    Then image 2 is at 181, 160

  @PLACE-007
  Scenario: Quarter turns rotate without resampling
    Given a patterned image "p.png" of 30 by 20 pixels
    And I open "p.png"
    When I open the context menu on image 1
    Then the menu has "Rotate 90° clockwise", "Rotate 90° counterclockwise" and "Rotate 180°"
    When I choose "Rotate 90° clockwise"
    Then the canvas is 20 by 30 pixels
    And the rendered canvas is exactly the source rotated a quarter turn clockwise

  @PLACE-008
  Scenario: Rotate with the rotation handle
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    And snapping is off
    When I select image 1
    And I drag from 200, 147 to 300, 200
    Then image 1 is rotated 90 degrees

  @PLACE-008
  Scenario: Shift limits the rotation handle to 15° steps
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I select image 1
    And I drag from 200, 147 to 287, 150 with Shift
    Then image 1 is rotated 60 degrees

  @PLACE-008
  Scenario: Rotate by an entered angle
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    When I rotate image 1 to 12.34 degrees
    Then image 1 is rotated 12.3 degrees
    And the undo label is "Undo Rotate image"
    And the rendered canvas at 151, 176 is "transparent"

  @PLACE-007
  Scenario Outline: Quarter turns stay pixel-aligned for any size
    Given a patterned image "q.png" of <width> by <height> pixels
    And I open "q.png"
    When I open the context menu on image 1
    And I choose "<command>"
    Then the canvas is <height> by <width> pixels
    And the rendered canvas is exactly the source rotated a quarter turn <direction>

    Examples:
      | width | height | command                      | direction        |
      | 31    | 20     | Rotate 90° clockwise         | clockwise        |
      | 31    | 20     | Rotate 90° counterclockwise  | counterclockwise |
      | 30    | 21     | Rotate 90° counterclockwise  | counterclockwise |
      | 7     | 4      | Rotate 90° clockwise         | clockwise        |

  @PLACE-002 @UNDO-004
  Scenario: A click selects without changing anything
    Given a composition of "a.png" and "b.png" with a custom canvas of 400 by 400 pixels
    And the view is at 100% with the canvas origin on screen at 50, 50
    When I drag from 200, 200 to 201, 201
    Then the project has 2 steps
    And image 2 is selected
    When I drag from 400, 200 to 401, 200
    Then the project has 2 steps