Skip to content

feat(crop): add a perspective mode that straightens a tilted picture - #764

Merged
KnifeLemon merged 2 commits into
mainfrom
feature/perspective-crop
Oct 29, 2026
Merged

KnifeLemon merged 2 commits into
mainfrom
feature/perspective-crop

Conversation

@SeoJCalive

@SeoJCalive SeoJCalive commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

What does this change?

Adds a perspective mode to the crop window. With the perspective toggle right next to the ratio button turned on, each of the four corners moves on its own, and saving straightens the chosen quad into a flat rectangle. It's for photos of documents, receipts or screens taken at an angle, the way a document scanner app works. Closes #699

  • Output size: the width is the longer of the top and bottom edges, the height the longer of the left and right edges, so nothing gets squeezed.
  • Corner limits: a corner dragged or nudged past the picture stops on its edge. A move into a crossing quad, a concave quad, or three corners on one line is refused, and the corner stays at its last valid spot.
  • Keyboard and screen readers: Tab picks a corner, and the arrow keys move it by one pixel, ten with Shift, the same as the frame. Each corner has a screen-reader name such as "Top-left corner", and the picked corner gets a ring on screen.
  • Several pictures: the mode stays on for the next picture, and each picture starts with the corners on its own edges. Turning the mode off brings back the rectangle frame, ratio and orientation from before it was turned on.
  • Saving: output path, format, quality, date keeping and conflict handling are the same as the current crop. Only the pixels differ. Corners that still form a whole-pixel rectangle (for example, not moved yet) save through the ordinary crop, without resampling.
  • PDF: a PDF crop only changes each page's crop box, so it can't be straightened. While a PDF is open the toggle is off and its tooltip says why; the mode comes back on for the next picture.
  • Not included: automatic document edge detection. Corners are placed by hand.

Choices I made

  • Toggle look: it started as a button labelled "Perspective", but with a custom ratio and several files the toolbar got too wide, so it's now a round icon-only toggle. Its name shows in the tooltip and to screen readers.
  • Dragging: a corner moves by how far the pointer moves, like the frame's handles, so grabbing it slightly off its dot doesn't make it jump. In perspective mode a press on the picture away from the corners does nothing and doesn't move the window. If the mode or the picture changes during a drag, the drag ends; this last rule applies to the rectangle frame too.
  • Minimum window width 680 → 720: in English with a custom ratio and several files, the toolbar before this change fit a 680 window with 4 DIP to spare. Adding the icon toggle (37 DIP) overflows it by 33 DIP and cuts off the save button, so I raised the minimum width to 720. The default window size (1040 × 720) is unchanged. If you'd rather move the toggle or shrink another button instead, let me know.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes

  • dotnet test

  • Tried it in the app (what you converted or clicked, and on which OS): in the crop window on headless Linux (X11): the toggle, dragging corners, refused moves, keyboard moves and the saved result.

  • Changed-area tests (crop window, crop service, crop math, pixelate window, localization): 318 tests, all passed.

  • Full App suite: 1,580 of 1,590 passed, 9 skipped, 1 failed. The failing RenderTests.Engines_page_shows_versions_descriptions_and_download_speed also fails without this PR (on 11769a89) in the same environment. Core: all 728 passed.

  • Build warnings: the same as the base commit, and dotnet format --verify-no-changes passes.

  • Do the new tests guard the feature: I made 33 mutations, each undoing one part of the change (the convexity check, stopping a corner on the edge, saving a plain rectangle without resampling, which buttons fade when disabled, moving by pointer travel, state changes during a drag, output size, key moves, screen-reader names, PDF handling and so on), and checked that a test fails for every one.

  • Saved result: straightening the code-drawn tilted four-colour rectangle gives a 573 × 391 px file whose quadrant centres are exactly red, green, yellow and blue.

How to check it yourself

  • In the app: open a photo of a document taken at an angle in the crop window and turn on the perspective toggle next to the ratio button. Drag the four corners onto the document's corners, or pick one with Tab and move it with the arrow keys (Shift for ten), check the size chip, and press Enter: a _cropped file appears in the same folder. Dragging a corner past another one doesn't move it, and dragging it off the picture stops it on the edge.
  • With tests: dotnet test tests/Filee.App.Tests -c Release --filter "FullyQualifiedName~CropOverlayTests|FullyQualifiedName~CropServiceTests|FullyQualifiedName~CropMathTests". CropServiceTests.A_perspective_crop_straightens_a_tilted_picture checks the saved file's size and quadrant colours, and the run writes the screens shown below to tests/Filee.App.Tests/bin/Release/net10.0/screenshots/crop-perspective-*.png.

Screenshots (UI changes)

A code-drawn tilted four-colour rectangle, straightened and saved in perspective mode.

Source picture (800 × 600) Saved file (573 × 391)

The crop window with the same picture. Before, the rectangle frame takes the grey around it too; after, the four corners sit on the picture's corners.

Before After
Dark theme
Before After
Several pictures: on the second picture the mode is still on, the corners start on its edges, and the corner picked with the keyboard has a ring

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none)
  • The PR does one thing (unrelated changes go in a separate PR)

Follow-up

  • With a custom ratio and several files, the toolbar already overflowed a 680 window in de, ru, fr, es, pt-BR, hi and ar before this PR (624–777 DIP against 610 available). Some still overflow at 720. I can put the measurements in a separate issue.

@github-actions github-actions Bot added the feature New features label Oct 29, 2026
@SeoJCalive SeoJCalive added the macos Run the macOS packages and tests in CI for this pull request label Oct 29, 2026

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@SeoJCalive 고마워요! 원근 변환 계산, 픽셀 매핑, EXIF 회전, 알파, PDF 처리, 여러 장 넘길 때의 흐름 모두 맞게 동작해요. 기존 사각형 자르기도 그대로예요. 하나만 고치면 머지할게요.

고쳐 주세요

  1. 모서리가 그림 밖으로 나가는 이동은 버리지 말고 가장자리에 붙여 주세요. (CropMath.MoveCorner / NudgeCorner)
    • 마우스: 모서리를 그림 귀퉁이 쪽으로 빠르게 끌면, 밖으로 나간 첫 이벤트가 통째로 무시돼요. 그래서 모서리가 가장자리 몇 픽셀 앞에서 멈춰요. 문서가 사진을 꽉 채운 경우, 저장된 그림 한쪽에 배경이 얇게 남아요.
    • 키보드: 가장자리에서 3px 떨어진 모서리에서 Shift+← (−10)를 누르면 아무 일도 안 일어나요. 사각형 틀의 CropMath.Nudge는 가장자리까지 붙여요.
    • 방법: 목표 위치를 먼저 [0, 1]로 자른 다음 볼록 검사를 하고, 꼬이거나 오목한 모양만 거부하면 돼요.
    • 테스트: 지금 "밖으로" 경우는 모서리가 이미 x=0에서 시작해서 이 상황이 빠져 있어요. 가장자리 근처에서 시작하는 경우를 넣어 주세요.

함께 고치면 좋은 것 (막지는 않아요)

  1. 한국어:
    • crop.perspective_pdf의 "그림", "PDF 쪽"은 ko.json의 다른 문구처럼 "이미지", "PDF 페이지"로 맞춰 주세요. perspective_tip의 "사진"과도 통일해 주세요.
    • 버튼 이름 "원근"만으로는 뜻이 약해요. 일본어 "遠近補正"처럼 "원근 보정"이 좋겠어요.
  2. 전역 Button.tool:disabled 스타일(불투명도 0.45)이 기존 도구 버튼에도 적용돼요. 예를 들어 비동기 명령이 도는 동안 건너뛰기 버튼이 흐려져요. 원근 토글에만 걸어 주세요.
  3. 사각형 그대로 저장할 때: 모서리를 안 움직인 사각형도 매번 Distort로 다시 샘플링해서 조금 흐려지고 느려요. 축에 맞는 직사각형이면 기존 Crop 경로로 보내 주세요.
  4. 파일 머리 주석: CropMath.cs(이제 CropQuad도 있음), CropCanvas.cs, 두 테스트 파일의 머리 주석을 새 내용에 맞게 고쳐 주세요.

창 최소 너비 680 → 720은 괜찮아요.

CI는 "Test UI on its own" 단계에서 취소돼서 결과가 없어요. 다음 push 때 다시 돌 거예요.

@SeoJCalive

Copy link
Copy Markdown
Collaborator Author

@KnifeLemon 리뷰 감사해요! 지금 반영하고 있어요.

  1. 모서리 가장자리 붙이기: 목표 위치를 먼저 이미지 안으로 자른 뒤 볼록 검사를 하도록 바꾸고 있고, 가장자리 근처에서 시작하는 마우스·키보드 경우를 시험에 넣고 있어요.
  2. 한국어: "원근 보정", "이미지", "PDF 페이지"로 맞추고 있어요.
  3. 흐림 스타일: 기존 도구 버튼에는 걸리지 않게 범위를 좁히고 있어요.
  4. 사각형 그대로 저장: 축에 맞는 직사각형은 기존 Crop 경로로 보내도록 고치고 있어요.
  5. 머리 주석: 새 내용에 맞게 고치고 있어요.

검증까지 마치면 올리고 다시 알려 드릴게요.

An icon-only perspective toggle next to the ratio button lets each of
the four corners move on its own. Saving straightens the chosen quad into a
rectangle whose width is the longer of the top and bottom edges and
whose height is the longer of the left and right edges. Output path,
format, quality and date rules are the same as the rectangle crop.

- Corners cannot be moved into a crossing, concave or flat shape, or
  outside the picture; a refused move leaves the corner where it was.
- Tab picks a corner, arrow keys move it by one pixel (Shift: ten),
  and each corner has a localized screen-reader name.
- The ratio dial and custom ratio are off while the mode is on. Each
  next picture starts with the corners on its own frame and the mode
  stays on. PDFs keep the rectangle crop and the toggle explains why.
- A corner follows the pointer's travel, so grabbing it off-centre
  doesn't make it jump. A drag ends when the mode or the picture
  changes under it, for the rectangle frame too.
- The crop window's minimum width goes from 680 to 720 so the toolbar
  with the new toggle, a custom ratio and several files still fits.
- Test pictures are drawn in code.

Closes #699
… rectangles unresampled

- A corner dragged or nudged past the picture now stops on its edge instead of
  the move being dropped; only twisted, concave or collinear shapes are refused.
- Corners still on a whole-pixel axis-aligned rectangle save through the plain
  crop, without a perspective resample.
- The disabled fade applies only to the ratio and perspective buttons, not to
  every toolbar button.
- Korean: "원근 보정", "이미지", "PDF 페이지".
- File header comments cover the perspective mode.
@SeoJCalive
SeoJCalive force-pushed the feature/perspective-crop branch from 9f46201 to 44c6830 Compare October 29, 2026 13:58
@SeoJCalive

Copy link
Copy Markdown
Collaborator Author

@KnifeLemon 꼼꼼히 봐 주셔서 감사해요! 말씀하신 다섯 가지 모두 반영했어요.

  1. 모서리 가장자리 붙이기: 목표 위치를 먼저 이미지 안으로 자른 뒤 볼록 검사를 하도록 바꿨어요. 이제 빠르게 끌거나 가장자리 3px 앞에서 Shift+←를 눌러도 모서리가 가장자리에 붙고, 꼬이거나 오목한 모양과 세 점이 한 줄인 경우만 거부해요. 가장자리 5px 앞에서 끄는 경우와 3px 앞에서 Shift+←를 누르는 경우를 화면 시험과 계산 시험에 넣었고, 가장자리에 붙인 결과가 꼬이는 경우는 여전히 거부되는지도 확인해요.
  2. 한국어: 버튼 이름을 "원근 보정"으로 바꾸고, 도구 설명과 PDF 안내는 "이미지", "PDF 페이지"로 맞췄어요.
  3. 흐림 스타일: 원근 모드가 끄는 비율 버튼과 원근 토글에만 걸리게 좁혔어요. 비율 버튼도 원근 모드에서 꺼지는 버튼이라 같이 두었어요. 건너뛰기 버튼처럼 다른 도구 버튼은 꺼져도 흐려지지 않는 것을 시험으로 확인해요.
  4. 사각형 그대로 저장: 모서리가 정수 픽셀 위의 축에 맞는 직사각형이면 기존 Crop 경로로 저장해요. 같은 영역을 일반 자르기로 저장한 결과와 픽셀까지 같은지 시험으로 확인해요.
  5. 머리 주석: CropMath.cs, CropCanvas.cs, 두 테스트 파일의 머리 주석을 원근 모드 내용까지 담도록 고쳤어요.

1번을 고치기 전과 후를 같은 조작으로 찍었어요. 이미지를 꽉 채운 문서에서 가장자리 60px 안쪽의 왼쪽 위 모서리를 귀퉁이 밖으로 한 번에 끌었어요.

고치기 전 고친 뒤
키보드: 가장자리 8px 앞의 모서리에서 Shift+← (누르기 전 → 누른 뒤, 5배 확대)
고치기 전 고친 뒤

PR 본문의 모서리 제한·저장 설명도 바뀐 동작에 맞게 고쳤어요.

@SeoJCalive
SeoJCalive requested a review from KnifeLemon October 29, 2026 14:32

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@SeoJCalive 다섯 가지 모두 확인했어요. 고마워요!

  • 모서리: 목표 위치를 먼저 [0, 1]로 자르고 나서 검사해요. 키보드 이동도 MoveCorner를 거치니까 똑같이 가장자리에 붙어요. 전후 스크린샷도 잘 봤어요.
  • 사각형 그대로 저장: 정수 픽셀에 맞는 직사각형은 일반 Crop 경로로 가고, 픽셀까지 같은지 시험도 있어요.
  • 흐림 스타일: 비율 버튼과 원근 토글에만 걸려요.
  • 한국어와 머리 주석: 모두 맞췄어요.

머지할게요. #699도 함께 닫혀요.

@KnifeLemon
KnifeLemon merged commit 68dd9eb into main Oct 29, 2026
10 checks passed
@SeoJCalive
SeoJCalive deleted the feature/perspective-crop branch October 29, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features macos Run the macOS packages and tests in CI for this pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: image crop

2 participants